Kodokon kodokon.com

การสังเกตการณ์: ล็อก การตรวจสุขภาพ และการปิดอย่างนุ่มนวล

ทำให้บริการของคุณสังเกตการณ์ได้และหยุดได้อย่างสะอาด ด้วยล็อกแบบ JSON โพรบ liveness/readiness และการจัดการ SIGTERM

9 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

ในระบบจริง ล็อกมีไว้สำหรับเครื่องจักร (Loki, Datadog, CloudWatch) ไม่ใช่มนุษย์ จงส่งออก หนึ่งอ็อบเจกต์ JSON ต่อหนึ่งบรรทัด (หนึ่งบรรทัด = หนึ่งอีเวนต์) พร้อมระดับ (level), เวลาแบบ ISO และฟิลด์ที่มีโครงสร้าง อย่าใช้การแทรกข้อความแบบอิสระที่ค้นหาไม่ได้เด็ดขาด รายละเอียดที่น้อยคนรู้ คือการเขียนไปยัง stdout เป็นแบบ ซิงโครนัส เมื่อปลายทางเป็นไฟล์หรือเทอร์มินัล แต่เป็นแบบอะซิงโครนัสเมื่อปลายทางเป็น pipe ดังนั้นล็อกเกอร์ที่พูดมากอาจบล็อกอีเวนต์ลูปได้ขึ้นอยู่กับปลายทาง นั่นคือเหตุผลทั้งหมดของไลบรารีอย่าง pino ที่ serialize ได้เร็วและสามารถถ่ายโอนภาระการเขียนออกไปได้

JAVASCRIPT
function log(level, message, fields = {}) {
  const entry = {
    level,
    message,
    time: new Date().toISOString(),
    pid: process.pid,
    ...fields,
  };
  process.stdout.write(JSON.stringify(entry) + "\n");
}

log("info", "server started", { port: 3000 });
log("error", "db unreachable", { retryInMs: 5000 });
ล็อกเกอร์แบบมีโครงสร้างที่เรียบง่ายที่สุด ค้นหาได้ทันที

จงแยกโพรบสองชนิดออกจากกัน Liveness ตอบคำถามว่า "ควรรีสตาร์ตโปรเซสหรือไม่?" มันต้องเรียบง่ายอยู่เสมอ โดยไม่พึ่งพาสิ่งภายนอกใด ๆ ส่วน Readiness ตอบคำถามว่า "ฉันรับทราฟฟิกได้หรือยัง?" มันอาจตรวจสอบฐานข้อมูล และเหนือสิ่งอื่นใดคือเปลี่ยนเป็น 503 ระหว่างการปิด เพื่อให้ load balancer ถอดอินสแตนซ์ออกก่อนที่มันจะดับ การสับสนระหว่างสองอย่างนี้ทำให้โปรเซสที่แข็งแรงถูกรีสตาร์ตทันทีที่สิ่งที่พึ่งพาสะดุด

JAVASCRIPT
import { createServer } from "node:http";

let ready = true;

const server = createServer((req, res) => {
  if (req.url === "/healthz") {
    res.writeHead(ready ? 200 : 503);
    res.end(ready ? "ok" : "draining");
    return;
  }
  res.end("hello");
});

server.listen(3000);

function shutdown() {
  ready = false;
  server.closeIdleConnections();
  server.close(() => process.exit(0));
  setTimeout(() => process.exit(1), 10_000).unref();
}

process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
readiness แบบไดนามิกและการปิดอย่างนุ่มนวลเมื่อได้รับ SIGTERM

ลำดับขั้นของการปิดอย่างนุ่มนวล คือ ตัวจัดการ (orchestrator) ส่ง SIGTERM คุณสลับ readiness เป็น 503 server.close() ปฏิเสธการเชื่อมต่อใหม่และรอให้คำขอที่กำลังดำเนินอยู่เสร็จสิ้น closeIdleConnections() ปิดการเชื่อมต่อ keep-alive ที่ว่างอยู่ ซึ่งมิฉะนั้นจะหน่วง close() ไว้ไม่มีที่สิ้นสุด ตัวจับเวลาที่มาร์กด้วย unref() ทำหน้าที่เป็นตาข่ายนิรภัย มันจะฆ่าโปรเซสหลังจาก 10 วินาที โดยตัวมันเองไม่ได้ขัดขวางไม่ให้โปรเซสออกก่อนหน้านั้น

JAVASCRIPT
process.on("uncaughtException", (err) => {
  const entry = {
    level: "fatal",
    message: err.message,
    stack: err.stack,
    time: new Date().toISOString(),
  };
  process.stderr.write(JSON.stringify(entry) + "\n");
  process.exit(1);
});

process.on("unhandledRejection", (reason) => {
  throw reason;
});
ทางเลือกสุดท้าย: บันทึกล็อก แล้วออก

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. อะไรคือความแตกต่างระหว่างโพรบ liveness กับ readiness?
    • ไม่มีเลย มันเป็นสองชื่อของการทดสอบเดียวกัน
    • Liveness ตรวจว่าโปรเซสกำลังทำงานอยู่ ส่วน readiness ตรวจว่ามันรับทราฟฟิกได้ รวมถึงสิ่งที่พึ่งพาด้วย
    • Readiness รีสตาร์ตคอนเทนเนอร์ ส่วน liveness ถอดมันออกจาก load balancer
  2. จริง ๆ แล้ว server.close() ทำอะไร?
    • มันตัดการเชื่อมต่อที่เปิดอยู่ทั้งหมดทันที
    • มันฆ่าโปรเซสหลังจากหน่วงเวลาคงที่ 30 วินาที
    • มันปฏิเสธการเชื่อมต่อใหม่และรอให้การเชื่อมต่อที่มีอยู่เสร็จสิ้น
  3. ตัวจัดการ uncaughtException ควรทำอะไร?
    • บันทึกข้อผิดพลาด แล้วจบการทำงานของโปรเซส
    • เพิกเฉยต่อข้อผิดพลาด Node จะกู้คืนได้เอง
    • ลองทำการดำเนินการที่ล้มเหลวซ้ำโดยอัตโนมัติ