Perbezaan entrypoint dan command dalam Docker Compose
Ketahui cara ENTRYPOINT dan command berinteraksi dalam Docker Compose. Fahami mengapa menetapkan entrypoint memadamkan CMD asal serta cara mengelakkan ralat konfigurasi.
Perintah vs entrypoint Docker Compose, dalam satu peraturan
Dalam Docker Compose, entrypoint: menetapkan program yang dijalankan dan command: menetapkan argumen yang diberikan kepada program tersebut. Proses kontena ialah senarai entrypoint dengan senarai command yang ditambah pada bahagian akhirnya. Setiap tingkah laku lain pada halaman ini berpunca daripada satu ayat tersebut.
Kedua-dua kunci ini dipetakan kepada dua arahan Dockerfile. entrypoint: menggantikan ENTRYPOINT imej tersebut. command: menggantikan CMD imej tersebut. Ia tidak bebas antara satu sama lain, dan di situlah pengguna sering tersangkut: menetapkan entrypoint: juga akan membuang CMD imej tersebut. Spesifikasi Compose menyatakan perkara ini secara terus. Jika entrypoint bukan null, Compose akan mengabaikan sebarang command lalai daripada imej tersebut.
Baca apa yang telah diisytiharkan oleh imej
Sebelum anda mengatasi sebarang tetapan, lihat apa yang disertakan oleh imej tersebut.
docker image inspect --format '{{json .Config.Entrypoint}}' postgres:16
docker image inspect --format '{{json .Config.Cmd}}' postgres:16Anda mendapat ["docker-entrypoint.sh"] dan ["postgres"], jadi kontena menjalankan docker-entrypoint.sh postgres. Skrip tersebut mencipta direktori data pada but pertama, membaca pemboleh ubah POSTGRES_*, menurunkan keistimewaan kepada pengguna postgres, dan akhirnya melaksanakan argumen yang diberikan kepadanya. Mengetahui bahagian mana yang ingin anda ubah adalah keputusan utama. Untuk menghantar flag kepada pangkalan data, anda menggantikan command:. Jika anda menggantikan entrypoint:, tiada satu pun persediaan tersebut akan dijalankan.
Empat kombinasi, ditunjukkan pada imej kecil
Bina imej yang tugas tunggalnya adalah untuk mencetak senarai argumen yang digunakan semasa ia dimulakan.
FROM alpine:3.20
ENTRYPOINT ["/bin/echo", "ep"]
CMD ["cmd"]docker build -t argdemo .services:
demo:
image: argdemoJalankan docker compose up selepas setiap suntingan dan baca baris tunggal yang direkodkan dalam log.
- Tiada kunci ditetapkan. Proses adalah
/bin/echo ep cmddan log menunjukkanep cmd. command: ["cmd2"]sahaja. Proses adalah/bin/echo ep cmd2. Entrypoint tidak disentuh dan hanya argumen yang berubah.entrypoint: ["/bin/echo", "ep2"]sahaja. Proses adalah/bin/echo ep2dan log menunjukkanep2.cmddaripada imej telah hilang, dan tiada amaran diberikan kepada anda.- Kedua-dua kunci ditetapkan. Proses adalah
/bin/echo ep2 cmd2. Ini adalah satu-satunya keadaan di mana anda mengawal keseluruhan senarai argumen.
Mengapa menetapkan entrypoint memadamkan CMD imej
CMD bagi sesuatu imej ditulis sebagai senarai argumen lalai untuk ENTRYPOINT imej tersebut. Gantikan entrypoint dan argumen tersebut kini menjadi milik program yang tidak lagi berjalan, jadi Compose menggugurkannya daripada membina baris perintah yang tidak pernah diniatkan oleh pencipta imej. docker run --entrypoint berkelakuan dengan cara yang sama, jadi ini adalah kelakuan Docker dan bukannya keanehan Compose.
Akibatnya adalah nyata. nginx:1.27 mengisytiharkan ENTRYPOINT ["/docker-entrypoint.sh"] dan CMD ["nginx", "-g", "daemon off;"]. Tetapkan entrypoint: /custom-init.sh dan skrip anda bermula dengan senarai argumen kosong. Skrip yang berakhir dengan exec "$@" yang biasa kemudiannya tidak mempunyai apa-apa untuk di-exec, jadi exec tidak melakukan apa-apa, skrip mencapai baris terakhirnya, dan kontena keluar dengan kod 0 tanpa sebarang mesej ralat di mana-mana. Masukkan semula argumen tersebut sendiri:
services:
web:
image: nginx:1.27
entrypoint: /custom-init.sh
command: ["nginx", "-g", "daemon off;"]Peraturan yang perlu diingat: setiap kali anda menetapkan entrypoint:, tentukan apakah command: yang sepatutnya dalam suntingan yang sama.
Bentuk exec dan bentuk shell, serta perbezaan dalam Compose
Dockerfile menerima dua sintaks. CMD ["nginx", "-g", "daemon off;"] ialah bentuk exec: binari dijalankan secara terus, tanpa melibatkan shell. CMD nginx -g "daemon off;" ialah bentuk shell: Docker menulis semula arahan tersebut sebagai /bin/sh -c 'nginx -g "daemon off;"', jadi shell dijalankan terlebih dahulu dan program anda menjadi anak prosesnya.
Compose tidak menyalin peraturan tersebut, dan ini mengejutkan ramai pengguna. Rentetan dalam command: dipecahkan kepada argumen dan dilaksanakan secara terus, tanpa pembungkus /bin/sh -c. Rujukan Compose menyatakan perkara ini dengan jelas: medan command tidak dijalankan dalam konteks SHELL yang ditakrifkan dalam imej, jadi jika anda memerlukan ciri shell, anda perlu memanggil shell sendiri.
Itulah sebabnya command: echo "hello $$HOSTNAME" mencetak teks literal hello $HOSTNAME. Tiada shell yang melihat rentetan tersebut, jadi tiada apa-apa yang mengembangkannya. Minta shell apabila anda memerlukannya:
services:
demo:
image: alpine:3.20
command: /bin/sh -c 'echo "hello $$HOSTNAME"'Isyarat, PID 1, dan docker compose down yang bersih
docker compose stop dan docker compose down menghantar SIGTERM kepada PID 1 di dalam setiap kontena, menunggu selama stop_grace_period, kemudian menghantar SIGKILL. Tempoh tangguh lalai ialah 10 saat.
PID 1 adalah istimewa dalam Linux. Kernel tidak menggunakan tindakan lalai bagi sesuatu isyarat kepada PID 1, jadi proses yang tidak memasang pengendali SIGTERM akan mengabaikan SIGTERM apabila ia berjalan sebagai PID 1. Ia akan kekal di situ sepanjang tempoh tangguh dan kemudian dimatikan secara paksa, yang akan memutuskan sebarang sambungan terbuka atau transaksi yang belum selesai.
Shell di hadapan program anda menjadikan perkara ini lebih kerap berlaku, kerana shell tersebut adalah PID 1 dan kebanyakan shell tidak memajukan isyarat kepada proses anak. Sesetengah shell menggantikan diri mereka dengan arahan terakhir dalam rentetan -c, jadi kadangkala program anda tetap mencapai PID 1. Perkara ini bergantung pada shell dan rentetan yang tepat, jadi jangan membuat andaian. Semak perkara tersebut:
docker compose exec -T web cat /proc/1/cmdline | tr '\0' ' '; echoJika PID 1 dipaparkan sebagai /bin/sh -c ... dan bukannya program anda, terdapat dua cara penyelesaian. Gunakan bentuk exec dalam imej, atau kekalkan shell dan serahkan proses tersebut dengan exec:
services:
web:
image: myapp:1.4
command: /bin/sh -c 'exec myapp --config /etc/myapp.toml'exec menggantikan proses shell dengan program anda dan bukannya melakukan fork kepada proses anak, jadi program anda mewarisi PID 1 dan menerima isyarat tersebut.
Sesetengah program menjana proses anak dan tidak pernah menamatkannya, yang meninggalkan proses zombi, kerana PID 1 juga berfungsi sebagai pemungut proses (reaper). Compose mempunyai suis untuk tujuan ini:
services:
web:
image: myapp:1.4
init: true
stop_grace_period: 30sinit: true menjalankan proses init kecil sebagai PID 1 yang memajukan isyarat kepada proses anda dan memungut proses anak. stop_grace_period memberikan ruang yang lebih luas untuk penutupan yang benar-benar perlahan. Jika program anda menjangkakan isyarat yang berbeza, stop_signal: SIGQUIT menukar isyarat yang dihantar oleh Compose. Semak perkara yang diminta oleh sesuatu imej dengan docker image inspect --format '{{.Config.StopSignal}}' nginx:1.27.
Timbunan (stack) di mana docker compose down sentiasa mengambil masa sepuluh saat bagi setiap servis menunjukkan bahawa tiada apa-apa yang mengendalikan SIGTERM. Selesaikan perkara itu sebelum menyalahkan alatan tersebut, dan lihat perbezaan antara docker compose down dan stop untuk mengetahui perkara yang dibuang oleh setiap subarahan.
Perbezaan antara exec dan shell yang sama muncul di satu lagi tempat. Healthcheck yang ditulis sebagai test: ["CMD", "curl", "-f", "http://localhost/"] menjalankan binari secara terus, manakala test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"] dijalankan melalui shell supaya || membawa maksud tertentu. Menulis healthcheck Compose yang gagal dengan jujur membincangkan selebihnya bagi bidang tersebut.
Menambah flag pada imej rasmi
Ini adalah tujuan utama kebanyakan pembaca. Anda mahukan satu flag tambahan pada postgres, dan anda tidak boleh mengganggu skrip permulaan.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
command: postgres -c max_connections=200 -c shared_buffers=256MB
volumes:
pgdata:Hanya command: yang berubah, jadi docker-entrypoint.sh masih berjalan dan masih melaksanakan apa yang anda berikan kepadanya. Semak hasilnya dan jangan sekadar membuat andaian:
docker compose up -d db
docker compose exec -T db psql -U postgres -c 'show max_connections;'Output sepatutnya menunjukkan 200. Jika ia masih menunjukkan 100, jalankan docker compose config dan sahkan command yang anda jangkakan ada dalam output yang digabungkan. Compose menggabungkan fail override dengan menggantikan command sepenuhnya, bukan dengan menambah kepadanya, jadi fail kedua yang turut menetapkan command: akan menang secara senyap.
${POSTGRES_PASSWORD} di atas dikembangkan oleh Compose pada hos daripada fail .env anda, sebelum kontena wujud. Fail env dan rahsia dalam Compose merangkumi tempat nilai tersebut boleh disimpan dengan selamat.
Menjalankan migrasi sekali jalan dengan docker compose run
docker compose run membina bekas baharu daripada definisi servis yang sama dan menggantikan arahan dengan apa sahaja yang anda taip selepas nama servis. Entrypoint imej tetap berjalan, jadi bekas tersebut disediakan dengan tepat seperti bekas yang berjalan lama.
docker compose run --rm app python manage.py migrate--rmmemadamkan bekas apabila arahan tamat. Tanpanya, setiap pelaksanaan akan meninggalkan bekas yang terhenti, yang boleh dilihat dalamdocker compose ps -a.- Port tidak diterbitkan. Bekas
runmengabaikanports:servis melainkan anda menambah--service-ports, jadi ia tidak akan bertembung dengan servis yang sudah berjalan. - Dependensi bermula dahulu. Apa-apa yang ada dalam
depends_onakan naik sebelum arahan anda, dan--no-depsmelangkau perkara tersebut. - Bekas mendapat nama yang dijana seperti
myproject-app-run-9f2c1a, jadi ia tidak akan bertembung dengan bekas servis.
Untuk menggantikan entrypoint juga, terdapat flag khusus untuknya:
docker compose run --rm --entrypoint /bin/sh app -c 'python manage.py migrate'Senarai argumen yang terhasil ialah /bin/sh -c 'python manage.py migrate', kerana perkataan selepas nama servis masih merupakan arahan. docker compose exec ialah alat lain dan ia berfungsi secara berbeza: ia menjalankan proses di dalam bekas yang sudah berjalan, dan ia mengabaikan kedua-dua entrypoint: dan command: sepenuhnya. Gunakan run untuk tugasan yang memerlukan bekas baharu, dan exec untuk melihat ke dalam bekas yang sedang aktif. Helaian rujukan arahan Compose meletakkan sub-arahan lain secara bersebelahan.
Mengapa kontena saya berhenti serta-merta?
Mulakan dengan kod keluar (exit code), kerana ia mempercepatkan pengecaman punca masalah.
docker compose ps -a
docker compose logs appKod keluar 0 dan tiada output. Perintah telah dijalankan dan selesai. Punca paling biasa ialah penggantian entrypoint: yang turut membuang CMD imej tersebut, menyebabkan entrypoint dijalankan dengan senarai argumen kosong dan tiada apa-apa untuk diproses.
Ralat yang berakhir dengan permission denied. Skrip tersebut tidak mempunyai bit boleh laku (executable bit) di dalam imej, biasanya kerana bit tersebut tidak pernah ditetapkan pada fail di dalam repositori. Tetapkan bit tersebut semasa fasa binaan (build time) menggunakan COPY --chmod=0755 entrypoint.sh /entrypoint.sh.
Ralat yang berakhir dengan no such file or directory bagi fail yang jelas kelihatan di dalam imej. Skrip tersebut mempunyai format penamat baris Windows. Baris pertamanya dibaca sebagai #!/bin/sh berserta bait carriage return, jadi kernel mencari penterjemah (interpreter) dengan nama yang mengandungi bait tersebut dan gagal menemuinya. Jalankan dos2unix entrypoint.sh, kemudian tambahkan * text eol=lf ke dalam .gitattributes supaya masalah ini tidak berulang.
executable file not found in $PATH. Binari yang dinamakan dalam command: tiada di dalam imej, atau anda menulis shell built-in seperti cd di tempat yang hanya menerima program sebenar.
Mendapatkan shell ke dalam imej yang entrypoint-nya gagal
Apabila entrypoint mati sebelum anda sempat memeriksa apa-apa, gantikannya:
docker compose run --rm --entrypoint /bin/sh appJika arahan tersebut memulangkan executable file not found in $PATH, imej tersebut langsung tidak mempunyai shell. Imej berasaskan Distroless dan scratch selalunya tidak menyertakan shell. Anda masih boleh membaca sistem fail dari luar tanpa memulakan entrypoint:
docker create --name probe myapp:1.4
docker export probe | tar -tv | head -40
docker rm probeApabila anda memerlukan kontena untuk terus hidup supaya anda boleh melampirkannya berulang kali, letakkannya pada proses yang tidak pernah tamat. Masukkan ini ke dalam fail override yang tidak anda commit:
services:
app:
entrypoint: ["tail", "-f", "/dev/null"]
command: []command: [] tidaklah wajib, memandangkan penetapan entrypoint: telah pun mengosongkan CMD imej tersebut, tetapi menulisnya merekodkan niat untuk sesiapa sahaja yang membaca fail itu kemudian. Naikkan kontena tersebut dan masuk ke dalamnya:
docker compose -f compose.yaml -f compose.debug.yaml up -d app
docker compose exec app /bin/shSekarang, jalankan entrypoint sebenar secara manual dan perhatikan di mana ia terhenti. Ini memberikan anda mesej ralat pada terminal anda dan bukannya di dalam kontena yang mati setengah saat yang lalu. Jika anda masih dalam proses menyusun stack pertama anda, stack Compose pertama pada VPS merangkumi susun atur fail yang diandaikan oleh semua perkara di atas.
FAQ
Mengapakah kontena saya berhenti serta-merta selepas docker compose up?
Semak docker compose ps -a untuk kod keluar (exit code). Keluar dengan kod 0 tanpa output biasanya bermakna anda menetapkan entrypoint: pada servis tersebut, yang turut memadamkan CMD imej, menyebabkan entrypoint dijalankan dengan senarai argumen kosong lalu tamat. Tambahkan semula argumen tersebut dengan command:. Ralat yang berakhir dengan permission denied bermakna skrip entrypoint tidak mempunyai bit boleh laksana (executable bit). Ralat yang berakhir dengan no such file or directory untuk fail yang wujud bermakna skrip tersebut mempunyai format penamat baris Windows, menyebabkan baris shebang merujuk kepada penterjemah yang tidak wujud.
Adakah menetapkan entrypoint dalam Compose memadamkan CMD imej?
Ya. Jika entrypoint bukan null, Compose mengabaikan sebarang arahan lalai yang diisytiharkan oleh imej. Itu adalah tingkah laku yang didokumenkan dan ia sepadan dengan docker run --entrypoint. Sebabnya ialah CMD imej ditulis sebagai argumen untuk ENTRYPOINT imej tersebut, jadi apabila anda menggantikan entrypoint, argumen lama tidak lagi mempunyai fungsi. Tetapkan command: dalam servis yang sama jika entrypoint baharu masih memerlukan argumen.
Adakah rentetan dalam arahan Compose dijalankan melalui shell?
Tidak. Berbeza dengan CMD dalam Dockerfile, rentetan dalam command: Compose dipecahkan kepada argumen dan dilaksanakan secara terus, tanpa pembungkus /bin/sh -c. Oleh itu, $VARIABLE tidak akan dikembangkan oleh shell di dalam kontena. Panggil shell sendiri apabila anda memerlukannya, seperti dalam command: /bin/sh -c 'echo "hello $$HOSTNAME"'. $$ yang digandakan akan melarikan (escape) tanda dolar supaya Compose menghantarnya ke kontena dan bukannya mengembangkannya pada hos.
Mengapakah docker compose down mengambil masa sepuluh saat untuk satu kontena?
Compose menghantar SIGTERM kepada PID 1, menunggu selama stop_grace_period (10 saat secara lalai), kemudian menghantar SIGKILL. Kernel tidak menggunakan tindakan isyarat lalai kepada PID 1, jadi program tanpa pengendali SIGTERM akan mengabaikan isyarat tersebut dan sentiasa menunggu sehingga tempoh penuh tamat. Ketahui apakah PID 1 yang sebenar dengan docker compose exec -T app cat /proc/1/cmdline | tr '\0' ' '. Jika ia adalah shell, tukar imej kepada bentuk exec atau tulis exec di dalam rentetan shell. Jika proses tersebut menghasilkan anak proses yang tidak pernah ditamatkan (reap), tetapkan init: true pada servis tersebut.