Healthcheck Docker Compose yang benar-benar berfungsi
Fahami cara healthcheck dinilai, sebab depends_on tidak menunggu servis sedia, serta contoh semakan readiness untuk Postgres dan aplikasi anda.
Perkara yang sebenarnya dilakukan oleh healthcheck Docker Compose
Healthcheck Docker Compose ialah satu arahan yang Docker jalankan di dalam bekas mengikut pemasa. Docker tidak membaca log anda, memantau port anda atau memeriksa senarai proses anda. Docker menjalankan arahan itu, membaca kod keluar, kemudian menyimpan satu keadaan pada bekas: starting, healthy atau unhealthy. Kod keluar 0 bermaksud sihat. Sebarang kod keluar lain bermaksud tidak sihat, manakala kod keluar 2 dikhaskan oleh Docker. Oleh itu, jangan kembalikan kod tersebut dengan sengaja.
Itulah keseluruhan mekanismenya. Hampir semua masalah healthcheck berpunca daripada perkara yang sama: arahan yang anda tulis menjawab soalan yang berbeza daripada soalan yang ingin anda tanyakan. Panduan ini menganggap anda sudah tahu cara menulis fail compose pada VPS, dan meneruskan penerangan dari titik apabila stack dimulakan dalam susunan yang salah.
services:
api:
image: ghcr.io/example/api:1.4.0
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 10s
timeout: 3s
retries: 5
start_period: 30sNilai test mempunyai dua bentuk yang berguna. Senarai yang bermula dengan CMD menjalankan arahan secara langsung tanpa shell. Oleh itu, pipe, && dan pengembangan pemboleh ubah tidak berfungsi. Senarai yang bermula dengan CMD-SHELL menghantar baki senarai sebagai satu rentetan kepada /bin/sh -c di dalam bekas. Gunakan bentuk ini apabila semakan memerlukan sintaks shell. Rentetan biasa dianggap sebagai CMD-SHELL. Senarai yang mengandungi tepat ["NONE"] membuang healthcheck yang telah terbina dalam imej melalui Dockerfile-nya.
Semakan dijalankan di dalam bekas. Oleh itu, setiap binari yang dinamakan mestilah wujud dalam imej tersebut. Sahkan perkara ini terlebih dahulu. Imej langsing yang tiada curl menghasilkan bekas yang kekal tidak sihat atas sebab yang tidak pernah muncul dalam log aplikasi. Uji secara manual:
docker compose exec api curl --versionBinari yang tiada akan memberikan respons OCI runtime exec failed: exec: "curl": executable file not found in $PATH: unknown. Imej berasaskan Alpine biasanya disertakan dengan wget BusyBox. Oleh itu, semakan tersebut menjadi ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/healthz"].
Cara interval, retries dan start_period digabungkan
Lima tetapan mengawal pemasaan. Nilai lalainya datang daripada Docker Engine, bukan daripada Compose.
interval: tempoh antara dua pemeriksaan selepas container melepasi tempoh permulaannya. Lalai ialah 30s.timeout: tempoh maksimum satu pelaksanaan pemeriksaan sebelum Docker menghentikannya dan mengira pelaksanaan itu sebagai kegagalan. Lalai ialah 30s.retries: bilangan kegagalan berturut-turut yang diperlukan sebelum keadaan bertukar kepadaunhealthy. Lalai ialah 3.start_period: tempoh kelonggaran selepas container dimulakan. Lalai ialah 0s.start_interval: kekerapan pemeriksaan dijalankan semasa tempoh permulaan. Lalai ialah 5s, dan tetapan ini memerlukan Docker Engine 25.0 atau lebih baharu.
Peraturan penting ialah: semasa tempoh permulaan, pemeriksaan yang gagal tidak dikira terhadap retries, dan container kekal dalam keadaan starting. Kali pertama pemeriksaan berjaya, container bertukar kepada healthy dan tempoh permulaan tamat serta-merta, walaupun sebahagian besar tempohnya belum digunakan. Jika tempoh permulaan tamat ketika pemeriksaan masih gagal, kiraan biasa bermula dan container memerlukan retries kegagalan berturut-turut sebelum ditandai sebagai unhealthy.
Oleh itu, masa kes terburuk dari permulaan container hingga unhealthy ialah start_period ditambah retries didarab dengan interval, kemudian ditambah timeout. Dengan nilai dalam fail di atas, pengiraannya ialah 30 ditambah 5 didarab dengan 13, iaitu 95 saat. Catat nombor itu sebelum anda menetapkan tamat masa pelancaran, kerana pelancaran yang berhenti selepas 60 saat tidak akan menyaksikan container ini mencapai keadaan akhir.
Kesilapan yang biasa berlaku ialah meningkatkan retries untuk menampung permulaan yang perlahan. Cara ini berkesan sekali, kemudian menjadi masalah berterusan: perkhidmatan yang memerlukan 8 percubaan untuk dimulakan kini membenarkan 8 kegagalan berturut-turut dalam persekitaran produksi sebelum sebarang tindakan diambil. Sebaliknya, gunakan start_period kerana tetapan itu hanya terpakai sebelum kejayaan pertama.
Mengapa depends_on sahaja tidak menjamin apa-apa
Bentuk ringkas depends_on merupakan punca utama kekeliruan.
api:
depends_on:
- dbMaksudnya hanya satu: mulakan bekas db sebelum bekas api. Compose menunggu sehingga bekas dicipta dan dimulakan. Compose tidak menunggu PostgreSQL selesai melakukan inisialisasi kali pertama dan tidak menunggu port 5432 menerima sambungan. Aplikasi anda bermula kira-kira satu saat kemudian, cuba menyambung ke port yang belum didengari oleh apa-apa proses, lalu keluar. Dalam log, anda akan melihat Connection refused, atau FATAL: the database system is starting up apabila pelayan sudah aktif tetapi masih dalam proses pemulihan.
Bentuk panjang ialah perkara yang sebenarnya diperlukan:
api:
depends_on:
db:
condition: service_healthy
restart: true
migrate:
condition: service_completed_successfullycondition mempunyai tiga nilai. service_started sama seperti bentuk ringkas. service_healthy menangguhkan perkhidmatan bergantung sehingga kebergantungan melaporkan status sihat. Ini hanya bermakna apabila kebergantungan tersebut mentakrifkan healthcheck, sama ada dalam fail compose atau dalam imejnya. service_completed_successfully menunggu bekas sekali jalan, seperti migrasi pangkalan data, keluar dengan status 0.
Dua medan tambahan berada bersebelahan dengan condition. restart: true memberitahu Compose supaya memulakan semula perkhidmatan ini selepas Compose mengemas kini perkhidmatan kebergantungan. required: false menurunkan kebergantungan yang tiada daripada ralat kepada amaran.
Kini had yang sering mengelirukan pengguna. Syarat ini dinilai apabila tindanan dimulakan. Syarat ini mengawal susunan permulaan, bukan peraturan penyeliaan. Jika pangkalan data dimulakan semula pada pukul tiga pagi, tiada apa-apa akan menilai semula service_healthy dan tiada apa-apa akan memulakan semula aplikasi anda untuk memenuhinya semula. Kod aplikasi anda masih perlu menyambung semula dengan sendiri. docker compose up --no-deps api sengaja melangkau keseluruhan mekanisme ini, begitu juga dengan memulakan bekas secara terus menggunakan docker start.
Tulis semakan yang menguji kesediaan, bukan sekadar kewujudan proses
Semakan seperti pgrep nginx hanya membuktikan bahawa entri jadual proses wujud. Ia tidak membuktikan sama ada servis tersebut boleh menjawab permintaan. Aplikasi web boleh membiarkan soket pendengar (listening socket) terbuka lama selepas pool pangkalan datanya mati, dan semakan proses akan kekal hijau sepanjang gangguan tersebut berlaku.
Arahkan kontena untuk melakukan tugas yang sepatutnya:
- Bagi servis HTTP, minta endpoint yang sebenar. Disebabkan
-f,curl -fsSakan keluar dengan kod 22 apabila pelayan menjawab dengan status 400 atau ke atas, jadi status 500 daripada aplikasi yang rosak akan dikira sebagai semakan yang gagal. Dokumentasi curl sendiri menyatakan-ftidak kalis-gagal (fail-safe), terutamanya untuk respons pengesahan 401 dan 407, jadi halakan semakan kepada endpoint yang tidak memerlukan log masuk. - Bagi PostgreSQL, gunakan
pg_isready, yang akan keluar dengan kod 0 apabila pelayan menerima sambungan, 1 apabila ia menolak sambungan, 2 apabila ia tidak memberi respons langsung, dan 3 apabila parameter yang anda berikan adalah salah. - Bagi Redis, gunakan
redis-cli ping, yang akan mencetakPONGdan keluar dengan kod 0. - Bagi MariaDB, imej rasmi menyertakan skrip
healthcheck.sh, danhealthcheck.sh --connect --innodb_initializedialah format yang didokumentasikan oleh penyelenggaranya.
pg_isready mempunyai satu perangkap yang perlu diketahui. Pada permulaan pertama dengan direktori data yang kosong, imej rasmi postgres menjalankan inisialisasi terhadap pelayan sementara yang hanya mendengar pada soket Unix. pg_isready tanpa argumen hos akan menggunakan soket tersebut, jadi ia boleh menjawab "menerima sambungan" walaupun port TCP 5432 masih tertutup kepada aplikasi anda. Halakan semakan kepada TCP secara eksplisit dan masalah tersebut akan selesai, kerana pelayan sementara tidak menjawab pada port tersebut.
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
start_period: 30sSimbol dolar berganda bukanlah satu kesilapan taip. Compose akan mengembangkan $VAR sendiri semasa membaca fail, yang akan memasukkan nilai daripada persekitaran hos anda ke dalam semakan tersebut. $$ akan melarikan (escape) simbol tersebut menjadi $ tunggal, supaya shell di dalam kontena akan mengembangkannya berdasarkan persekitaran kontena itu sendiri.
Stack postgres dan aplikasi yang bermula mengikut urutan yang betul
services:
db:
image: postgres:17.5
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
api:
image: ghcr.io/example/api:1.4.0
environment:
DATABASE_URL: postgres://appuser:${DB_PASSWORD}@db:5432/appdb
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 10s
timeout: 3s
retries: 5
start_period: 30s
ports:
- "127.0.0.1:8080:8080"
restart: unless-stopped
volumes:
pgdata:Jalankan stack tersebut dan perhatikan perubahan status:
docker compose up -d
docker compose psLajur STATUS memaparkan status kesihatan di dalam kurungan. Pasangan yang sihat akan memaparkan Up 41 seconds (healthy) pada kedua-dua baris. Semasa pangkalan data masih dalam proses inisialisasi, db akan memaparkan Up 4 seconds (health: starting) dan api tidak akan kelihatan dalam senarai. Compose telah pun mencipta kontena api dan hanya menahan permulaannya sehingga db melaporkan status sihat. Ia tidak kelihatan kerana docker compose ps hanya memaparkan kontena yang sedang berjalan secara lalai. Jalankan docker compose ps -a pada ketika itu dan api akan muncul dalam status created.
Untuk melihat sebab semakan lulus atau gagal, baca log kesihatan:
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q db)"Docker menyimpan beberapa hasil terakhir, setiap satunya dengan masa mula, masa tamat, ExitCode dan Output arahan tersebut. Output yang disimpan akan dipotong, jadi semakan yang mencetak badan halaman yang besar akan memberikan entri log yang tidak berguna. Pastikan semakan anda ringkas.
Tindakan Docker apabila kontena menjadi tidak sihat
Tiada apa-apa. Inilah jawapan yang paling mengejutkan orang.
Docker Engine pada satu hos tidak memulakan semula kontena yang tidak sihat. Dasar restart: unless-stopped bertindak balas apabila proses utama keluar, manakala kontena yang tidak sihat belum keluar. Kontena itu boleh kekal pada unhealthy selama seminggu sementara Compose membiarkannya begitu sahaja. Mod Swarm menggantikan tugas yang tidak sihat, tetapi tindanan Compose biasa pada satu pelayan tidak berbuat demikian.
Terdapat dua pilihan yang munasabah. Jadikan proses keluar apabila proses itu mengetahui bahawa dirinya rosak, supaya dasar mula semula mempunyai keadaan untuk ditangani. Atau pantau keadaan tersebut dari luar dan keluarkan amaran mengenainya. Menetapkan pemantau Uptime Kuma pada titik akhir yang sama seperti yang dipanggil oleh healthcheck bermakna kebergantungan yang rosak akan muncul di kedua-dua tempat, dan anda akan mengetahuinya daripada pemantau, bukan daripada pengguna. Jika trafik sampai ke aplikasi melalui proksi songsang Traefik, ingat bahawa pandangan proksi terhadap backend adalah berasingan daripada keadaan kesihatan Docker. Oleh itu, satu tidak menggantikan yang lain.
Menyahpepijat pemeriksaan yang tidak pernah menjadi sihat
Jalankan perintah tepat itu sendiri, dalam kontena yang sama, dan semak kod keluar:
docker compose exec api curl -fsS http://localhost:8080/healthz; echo "exit=$?"exit=0 di sini, apabila kontena masih melaporkan status tidak sihat, bermakna test compose anda berbeza daripada yang baru ditaip. Biasanya, ini berlaku kerana CMD digunakan sedangkan sintaks shell diperlukan.
Dua kesilapan merangkumi kebanyakan kes yang lain. Kesilapan pertama ialah port yang salah. Pemeriksaan kesihatan dijalankan di dalam kontena, jadi pemeriksaan itu mesti menggunakan port kontena, bukan port hos yang diterbitkan. Dengan ports: - "8080:3000", aplikasi mendengar pada port 3000. Pemeriksaan terhadap http://localhost:8080 akan gagal selama-lamanya walaupun laman berfungsi dengan baik dalam pelayar. Kesilapan kedua ialah hos yang salah. Dalam pemeriksaan, localhost merujuk kepada kontena yang sama. Ini betul untuk memeriksa kontena itu sendiri tetapi salah untuk memeriksa kontena bersebelahan. Dalam keadaan itu, anda memerlukan nama perkhidmatan, contohnya db.
Satu lagi kes patut dinamakan: pemeriksaan kesihatan berjaya sedangkan pengguna melihat ralat. Ini berlaku apabila titik akhir mengembalikan 200 statik tanpa menyentuh sebarang komponen sebenar. Titik akhir kesediaan yang tidak pernah membuat pertanyaan kepada pangkalan data tidak dapat memberitahu anda bahawa pangkalan data telah terputus. Jadikan titik akhir itu menjalankan satu pertanyaan sebenar yang ringan.
FAQ
Mengapakah aplikasi saya masih gagal disambungkan apabila depends_on menyatakan pangkalan data sihat?
Kerana condition: service_healthy dinilai sekali sahaja apabila tindanan dimulakan. Selepas itu, ia tidak memantau apa-apa. Jika bekas pangkalan data dimulakan semula kemudian, Compose tidak memulakan semula aplikasi anda untuk memenuhi syarat itu sekali lagi. Oleh itu, kod aplikasi anda memerlukan logik penyambungan semula dan percubaan semula sendiri. Syarat ini juga tidak berfungsi apabila anda memulakan satu bekas dengan docker start atau docker compose up --no-deps.
Adakah saya memerlukan healthcheck jika imej sudah mentakrifkan healthcheck?
Biasanya tidak. Mengatasinya juga sering merupakan langkah yang kurang baik kerana penyelenggara imej mengetahui maksud kesediaan untuk perisian tersebut. Tambahkan healthcheck sendiri hanya apabila pemeriksaan imej tidak sesuai dengan persediaan anda, contohnya apabila pemeriksaan itu menguji port yang telah anda pindahkan. Untuk melumpuhkan healthcheck imej, tetapkan test: ["NONE"] atau disable: true pada perkhidmatan.
Patutkah healthcheck menggunakan curl atau wget?
Gunakan yang sudah tersedia dalam imej, dan sahkannya dengan docker compose exec <service> curl --version sebelum bergantung padanya. Banyak imej berasaskan Debian tidak mempunyai kedua-duanya. Imej berasaskan Alpine mempunyai wget BusyBox. Jangan tambahkan pakej ke dalam imej hanya untuk menjalankan healthcheck apabila perisian tersebut menyediakan klien sendiri, seperti pg_isready atau redis-cli.
Adakah bekas yang tidak sihat dimulakan semula secara automatik?
Tidak oleh Docker Engine pada hos tunggal. Dasar mula semula bertindak balas apabila proses keluar, bukan berdasarkan keadaan kesihatan. Oleh itu, bekas yang tidak sihat kekal berjalan dan rosak sehingga sesuatu yang lain mengambil tindakan. Sama ada jadikan proses keluar apabila mengesan kegagalan, atau jalankan pemantau luaran yang memberikan amaran tentang keadaan tersebut.
Berapa lamakah tempoh start_period yang sesuai?
Tempohnya mestilah mencukupi untuk permulaan pertama yang sah dan paling perlahan yang telah anda ukur, serta mempunyai sedikit margin. Ukur tempoh itu dengan docker compose up menggunakan volum kosong kerana permulaan pertama pangkalan data jauh lebih perlahan berbanding setiap permulaan selepasnya. Tempoh mula yang terlalu panjang hanya melambatkan keputusan pertama unhealthy. Bilangan percubaan semula yang terlalu tinggi melemahkan pemeriksaan sepanjang hayat bekas, dan ini merupakan kegagalan yang lebih buruk.