Docker Healthcheck: ให้ Container กู้ตัวเองเมื่อแอปค้าง

ตั้ง Docker Healthcheck ใน Dockerfile และ Compose ให้บริการรอฐานข้อมูลพร้อม อ่านผลด้วย docker inspect และรีสตาร์ต Container ที่ unhealthy อัตโนมัติด้วย autoheal

โดย ทีมงาน THAI DATA CLOUD อ่าน 14 นาที

แชร์FacebookXLINELinkedIn
Docker Healthcheck — Docker Healthcheck: ให้ Container กู้ตัวเองเมื่อแอปค้าง
Docker Healthcheck: ให้ Container กู้ตัวเองเมื่อแอปค้าง

คำตอบสั้น ๆ: ตั้ง Docker Healthcheck ใน Dockerfile และ Compose ให้บริการรอฐานข้อมูลพร้อม อ่านผลด้วย docker inspect และรีสตาร์ต Container ที่ unhealthy อัตโนมัติด้วย autoheal

คู่มือนี้อธิบาย Docker Healthcheck ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย

Docker Healthcheck คือคำสั่งตรวจสุขภาพที่ Docker รันซ้ำในคอนเทนเนอร์เป็นระยะ เพื่อบอกว่าแอปข้างในยังตอบงานได้จริงหรือไม่ ผลที่ได้คือสถานะ starting, healthy หรือ unhealthy ที่เห็นใน docker ps สิ่งที่ผู้ดูแลหลายคนเข้าใจผิดคือ Docker แบบเครื่องเดียว (ไม่ใช่ Swarm) ไม่รีสตาร์ตคอนเทนเนอร์ที่ unhealthy ให้เอง Restart policy ทำงานเฉพาะเมื่อโปรเซสหลักจบการทำงาน แอปที่ค้างแต่โปรเซสยังอยู่จึงค้างต่อไปเรื่อย ๆ ถ้าไม่ได้ตั้งอะไรเพิ่ม

คู่มือนี้เหมาะกับผู้ดูแล Linux Server ที่รันเว็บหรือแอปด้วย Docker Compose แล้วเคยเจอแอปค้างตอนกลางคืนจนต้องเข้ามาสั่ง restart เอง เมื่อทำครบ คุณจะมี endpoint ตรวจสุขภาพที่เหมาะสม ตั้ง Docker Healthcheck ได้ทั้งใน Dockerfile และ Compose ให้บริการรอฐานข้อมูลพร้อมก่อนเริ่ม อ่านผลตรวจเป็น ตั้ง Restart policy ให้ถูก และให้คอนเทนเนอร์ที่ unhealthy ถูกรีสตาร์ตอัตโนมัติอย่างปลอดภัย

สิ่งที่ต้องเตรียม

  • Docker Engine และ Docker Compose v2 บน Linux Server ถ้ายังไม่มีให้ติดตั้งตาม คู่มือติดตั้ง Docker และ Docker Compose บน Ubuntu ตัวเลือก start-interval ต้องใช้ Docker Engine 25.0 ขึ้นไป ตรวจด้วย docker version
  • แอปที่มีหน้า HTTP หรือคำสั่งที่บอกได้ว่าพร้อมให้บริการ เช่น /healthz ถ้ายังไม่มีให้ขอให้นักพัฒนาเพิ่มตามขั้นตอนที่ 1
  • รู้ว่าในอิมเมจมีเครื่องมือใดให้ใช้ อิมเมจแบบ slim หรือ distroless หลายตัวไม่มี curl หรือแม้แต่ shell
  • สิทธิ์ใช้คำสั่ง docker บนเครื่อง (root หรือผู้ใช้ในกลุ่ม docker)
ลำดับสถานะ Docker Healthcheck จาก starting เป็น healthy เมื่อตรวจผ่าน เป็น unhealthy เมื่อล้มเหลวติดกัน และถูก autoheal รีสตาร์ต
Docker บอกได้ว่าคอนเทนเนอร์ unhealthy แต่ต้องมี autoheal หรือ Swarm จึงจะรีสตาร์ตให้

ขั้นตอนที่ 1: ออกแบบ endpoint ที่ Docker Healthcheck จะเรียก

Docker Healthcheck ที่ดีตอบคำถามเดียวว่า “คอนเทนเนอร์นี้รับงานได้ไหม” จึงควรเรียกหน้าที่เบาและเร็ว เช่น GET /healthz ที่ตอบ 200 เมื่อแอปทำงาน และตอบ 500 หรือ 503 เมื่อมีปัญหาภายในตัวเอง เช่น worker ค้างหรือคิวเต็ม

ระวังการผูกการตรวจกับระบบภายนอก ถ้า /healthz ตรวจฐานข้อมูลด้วย เมื่อฐานข้อมูลช้าไม่กี่นาที ทุกคอนเทนเนอร์ของเว็บจะ unhealthy พร้อมกัน และถ้าตั้งรีสตาร์ตอัตโนมัติไว้ จะรีสตาร์ตกันทั้งชุดโดยไม่ช่วยอะไร ให้ตรวจเฉพาะสิ่งที่การรีสตาร์ตคอนเทนเนอร์นั้นแก้ได้ ส่วนสถานะของฐานข้อมูลให้ฐานข้อมูลมี Docker Healthcheck ของตัวเอง

ขั้นตอนที่ 2: เพิ่ม Docker Healthcheck ใน Dockerfile

คำสั่ง HEALTHCHECK ใน Dockerfile ทำให้ทุกคอนเทนเนอร์ที่สร้างจากอิมเมจนี้มี Docker Healthcheck ติดไปด้วย ตัวอย่างแอป Node.js บนอิมเมจ Alpine ซึ่งมี wget ของ BusyBox อยู่แล้ว

FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --start-interval=5s --retries=3 \
  CMD wget -qO- http://127.0.0.1:3000/healthz || exit 1
CMD ["node", "server.js"]

ค่าเริ่มต้นตาม เอกสาร Dockerfile reference คือ --interval=30s, --timeout=30s, --start-period=0s, --start-interval=5s และ --retries=3 ความหมายคือ

  • interval: รันการตรวจทุกกี่วินาที
  • timeout: การตรวจที่ใช้เวลานานกว่านี้นับเป็นล้มเหลว
  • start-period: ช่วงเริ่มต้นที่ยังไม่นับความล้มเหลว ให้แอปที่บูตช้ามีเวลาโหลด ระหว่างนี้ Docker ตรวจทุก start-interval
  • retries: ล้มเหลวติดกันกี่ครั้งจึงเป็น unhealthy

คำสั่งตรวจต้องจบด้วยรหัส 0 (healthy) หรือ 1 (unhealthy) ห้ามใช้รหัส 2 เพราะ Docker สงวนไว้ จึงต่อท้ายด้วย || exit 1 ถ้ามี HEALTHCHECK หลายบรรทัด จะใช้เฉพาะบรรทัดสุดท้าย และ Docker เก็บข้อความผลลัพธ์ได้เพียง 4096 ไบต์แรก ถ้าอิมเมจต้นทางมี Docker Healthcheck ที่ไม่ต้องการ ใช้ HEALTHCHECK NONE เพื่อปิด

ขั้นตอนที่ 3: ตั้ง Docker Healthcheck ใน Compose และให้บริการรอกัน

ถ้าไม่ได้สร้างอิมเมจเอง ให้ตั้ง Docker Healthcheck ในไฟล์ Compose แทน แล้วใช้ depends_on แบบ condition: service_healthy ให้เว็บเริ่มหลังฐานข้อมูลพร้อมจริง ไม่ใช่แค่คอนเทนเนอร์ถูกสร้าง

# /opt/app/compose.yaml
services:
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  web:
    image: registry.example.com/shop-web:1.8.2
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "127.0.0.1:3000:3000"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/healthz || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s
    labels:
      autoheal: "true"

secrets:
  db_password:
    file: ./db_password.txt

volumes:
  db-data:
cd /opt/app
docker compose up -d --wait
docker compose ps

--wait ทำให้คำสั่งรอจนทุกบริการ running หรือ healthy ก่อนกลับมาที่ prompt เหมาะกับสคริปต์ Deploy ที่ต้องรู้ว่าระบบขึ้นสำเร็จ รายละเอียดฟิลด์ทั้งหมดอยู่ใน เอกสาร Compose services: healthcheck

ข้อควรระวังกับฐานข้อมูลอื่น: redis-cli ping ใช้ตรวจ Redis ได้ ส่วน MySQL การใช้ mysqladmin ping จะได้รหัส 0 แม้รหัสผ่านผิด เพราะคำสั่งนี้ตรวจเพียงว่าเซิร์ฟเวอร์ทำงาน ถ้าต้องการตรวจลึกกว่านั้นให้ใช้คำสั่ง SQL จริง และถ้าใช้ตัวแปรของคอนเทนเนอร์ในคำสั่งตรวจ ให้เขียน $$ แทน $ เพื่อไม่ให้ Compose แทนค่าเองตอนอ่านไฟล์

ขั้นตอนที่ 4: อ่านผลของ Docker Healthcheck

docker ps --format 'table {{.Names}}\t{{.Status}}'
docker inspect --format '{{json .State.Health}}' app-web-1 | jq
docker events --filter event=health_status
ผลคำสั่ง docker inspect แสดง Docker Healthcheck สถานะ unhealthy พร้อม FailingStreak และข้อความ HTTP 503 จาก wget
Output บอกสาเหตุได้ทันที ในตัวอย่างนี้แอปตอบ 503 ไม่ใช่คำสั่งตรวจเสีย

docker inspect แสดงสถานะของ Docker Healthcheck จำนวนครั้งที่ล้มเหลวติดกัน (FailingStreak) และผลตรวจล่าสุดไม่กี่ครั้งพร้อมข้อความที่คำสั่งตรวจพิมพ์ออกมา ใช้หาสาเหตุได้ทันทีว่าแอปตอบช้าหรือตอบ Error ส่วน docker events แสดงทุกครั้งที่สถานะเปลี่ยน เหมาะเปิดดูระหว่างทดสอบ ถ้าต้องการดู Log ของแอปช่วงเดียวกันใช้ docker logs --since 10m app-web-1 และตั้งขนาด Log ไม่ให้ดิสก์เต็มตาม คู่มือจำกัดขนาด Log ของ Docker

ขั้นตอนที่ 5: ตั้ง Restart policy ให้ทำงานคู่กับ Docker Healthcheck

Restart policy คือด่านแรกที่จัดการกับคอนเทนเนอร์ที่โปรเซสหลักตายหรือเครื่องรีบูต ทำงานแยกจาก Docker Healthcheck

ตารางเปรียบเทียบ Restart policy no, on-failure, always และ unless-stopped ว่ารีสตาร์ตเมื่อไร และไม่มีแบบใดดูสถานะ Docker Healthcheck
ทุก policy ดูเฉพาะโปรเซสที่จบการทำงาน ไม่มีแบบใดรีสตาร์ตคอนเทนเนอร์ที่ unhealthy
  • ใช้ unless-stopped กับบริการที่ต้องทำงานตลอด เพราะกลับมาเองหลังรีบูตเครื่อง แต่ถ้าผู้ดูแลสั่ง docker stop ไว้ จะไม่ถูกเปิดขึ้นมาเอง
  • ใช้ on-failure:5 กับงานที่ควรหยุดเมื่อพังซ้ำ ๆ เพื่อไม่ให้วนรีสตาร์ตไม่รู้จบ
  • Restart policy เริ่มมีผลเมื่อคอนเทนเนอร์ทำงานได้อย่างน้อย 10 วินาที เพื่อกันการวนรีสตาร์ตของคอนเทนเนอร์ที่เปิดไม่ขึ้นเลย (ดู Start containers automatically) และ Docker เว้นระยะก่อนรีสตาร์ตแต่ละครั้งเพิ่มเป็นสองเท่า เริ่มที่ 100 มิลลิวินาที สูงสุด 1 นาที

ขั้นตอนที่ 6: รีสตาร์ตคอนเทนเนอร์ที่ Docker Healthcheck แจ้ง unhealthy ด้วย autoheal

เพราะ Docker ไม่รีสตาร์ตคอนเทนเนอร์ที่ Docker Healthcheck แจ้งว่า unhealthy ให้เอง วิธีที่ใช้กันแพร่หลายคือรันคอนเทนเนอร์ willfarrell/autoheal ซึ่งเฝ้าดูสถานะผ่าน Docker API แล้วสั่งรีสตาร์ตคอนเทนเนอร์ที่ติด Label autoheal=true (ไฟล์ Compose ในขั้นตอนที่ 3 ติด Label นี้ให้บริการ web แล้ว) เพิ่มบริการนี้ในไฟล์เดิม

  autoheal:
    image: willfarrell/autoheal:latest
    restart: always
    network_mode: none
    environment:
      AUTOHEAL_CONTAINER_LABEL: autoheal
      AUTOHEAL_INTERVAL: 10
      AUTOHEAL_START_PERIOD: 60
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

ค่า AUTOHEAL_INTERVAL คือความถี่ที่ตรวจสถานะ (ค่าเริ่มต้น 5 วินาที) และ AUTOHEAL_START_PERIOD คือเวลารอก่อนเริ่มตรวจหลัง autoheal เปิด ตามเอกสารของ docker-autoheal ควร pin รุ่นของอิมเมจหลังทดสอบแล้วแทนการใช้ latest ตลอดไป

คำเตือน: การเมานต์ /var/run/docker.sock ให้คอนเทนเนอร์ใดก็ตาม เท่ากับให้สิทธิ์ระดับ root ของเครื่อง ใช้เฉพาะอิมเมจที่เชื่อถือได้ ไม่เปิดพอร์ตใด ๆ ให้ autoheal (ตัวอย่างนี้ใช้ network_mode: none) และทดสอบด้วยการทำให้แอป unhealthy ในเครื่องทดสอบก่อน เช่น หยุดโปรเซสภายในชั่วคราว แล้วดู docker events ว่า autoheal รีสตาร์ตจริง

ระวังการรีสตาร์ตวนที่ซ่อนปัญหา

การรีสตาร์ตอัตโนมัติจาก Docker Healthcheck ช่วยให้บริการกลับมาเร็ว แต่ไม่ได้แก้ต้นเหตุ ถ้า Docker Healthcheck ล้มเพราะหน่วยความจำรั่ว แอปจะกลับมาค้างอีกในไม่กี่ชั่วโมง ให้นับจำนวนการรีสตาร์ตด้วย docker inspect --format '{{.RestartCount}}' app-web-1 (นับเฉพาะที่ Restart policy สั่ง) และดู Log ของ autoheal ด้วย docker logs autoheal ถ้ารีสตาร์ตบ่อย ให้ส่งข้อมูลให้นักพัฒนาแก้ที่แอป และตั้งการแจ้งเตือนภายนอก เช่น Uptime Monitoring หรือ Healthchecks สำหรับงานตั้งเวลา เพื่อให้มีคนรู้ แทนที่ระบบจะซ่อมตัวเองเงียบ ๆ ไปเรื่อย ๆ

เช็กลิสต์การตั้ง Docker Healthcheck ที่ดี เช่น endpoint เบา มี start_period ใช้ 127.0.0.1 และไม่ผูกกับฐานข้อมูลภายนอก
การตรวจที่ดีบอกปัญหาของคอนเทนเนอร์นั้นเอง และไม่ทำให้ทั้งระบบรีสตาร์ตพร้อมกัน

ปัญหาที่พบบ่อยเกี่ยวกับ Docker Healthcheck

ตั้ง restart: always แล้ว ทำไมคอนเทนเนอร์ unhealthy ไม่ถูกรีสตาร์ต?

Restart policy ดูเฉพาะการที่โปรเซสหลักจบการทำงาน ไม่ได้ดูผล Docker Healthcheck คอนเทนเนอร์ที่ unhealthy แต่โปรเซสยังอยู่จึงไม่ถูกแตะ ต้องใช้ autoheal ตามขั้นตอนที่ 6 หรือใช้ Docker Swarm ที่แทนที่ task ที่ unhealthy ให้

Docker Healthcheck ขึ้น unhealthy ตลอดทั้งที่เปิดเว็บได้ ทำไม?

สาเหตุที่พบบ่อยคือในอิมเมจไม่มี curl หรือ wget คำสั่งจึงล้มทุกครั้ง หรือเรียก localhost แล้วได้ IPv6 ขณะที่แอปฟังเฉพาะ IPv4 ให้ดูข้อความใน Output ของ docker inspect และใช้ 127.0.0.1 แทน localhost

Docker Healthcheck ทำให้เครื่องทำงานหนักขึ้นไหม?

ทุกการตรวจคือการรันโปรเซสใหม่ในคอนเทนเนอร์ ถ้าตั้ง interval ถี่มากกับคอนเทนเนอร์จำนวนมาก จะเห็นภาระเพิ่มขึ้น ค่า 30 วินาทีเหมาะกับงานส่วนใหญ่ ใช้ start-interval ที่ถี่กว่าเฉพาะช่วงเริ่มต้น

depends_on แบบ service_healthy ใช้ตอนรีสตาร์ตฐานข้อมูลด้วยหรือไม่?

เงื่อนไขนี้ใช้ตอน Compose สร้างและเริ่มบริการ ถ้าต้องการให้ Compose รีสตาร์ตบริการ web หลังอัปเดตฐานข้อมูล ให้เพิ่ม restart: true ใต้ depends_on ของ db ซึ่งรองรับใน Docker Compose 2.17.0 ขึ้นไป แอปเองก็ควรเชื่อมต่อฐานข้อมูลใหม่ได้เองเมื่อการเชื่อมต่อหลุด

ถ้าต้องการให้ทีมช่วยวางโครงสร้าง Docker Compose การตรวจสุขภาพ และการแจ้งเตือนของระบบบนเซิร์ฟเวอร์ ติดต่อทีมงาน THAI DATA CLOUD หรือดูรายละเอียดบริการ Cloud Linux Server

อ่านเพิ่มเติมเกี่ยวกับ Docker Healthcheck

แชร์FacebookXLINELinkedIn

มีคำถามเรื่องบริการ? ติดต่อทีมงาน