เรียน Go ภาษาง่าย 📘

📊

เจาะลึก Observability + Production Ops ฉบับภาษาง่าย

slog, health check, graceful shutdown, Docker

7 บท
10 ข้อสอบ
1. Config 3 ชั้น

env > flag > file อย่า hardcode รหัส:

go
port := os.Getenv("PORT")
if port == "" { port = "8080" }
dsn := os.Getenv("DATABASE_URL") // รหัสห้าม commit ลง git
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


2. Log ด้วย slog (มากับ std)
go
import "log/slog"
slog.Info("start", "port", port)
slog.Error("db fail", "err", err) // อย่า log token เต็ม มาสก์ก่อน

อยากได้ JSON ส่งไป log server ใช้ slog.New(slog.NewJSONHandler(os.Stdout, nil))

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


3. Metric + Trace + Health
  • /healthz = ตอบ 200 ถ้ายังหายใจ (ให้ K8s/ALB เช็ก) + /readyz เช็ก DB ด้วย PingContext
  • metric นับ request/latency/error (เช่น Prometheus) ดูกราฟก่อนลูกค้าบ่น
  • trace ข้าม service ใช้ request-id ส่งผ่าน context.WithValue (เก็บแค่ id เล็กๆ ไม่ยัดของใหญ่)
go
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) })
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


4. Graceful shutdown (ตายสวย)
go
srv := &http.Server{Addr: ":"+port, Handler: mux}
go srv.ListenAndServe()

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx) // รอแขกเก่ากลับก่อนค่อยปิด

ทุก call นอก (DB/HTTP) ต้องมี timeout ผ่าน context ไม่งั้น shutdown ค้าง

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


5. Docker เล็ก + CI เช็ก
dockerfile
FROM golang:1.27 AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o app .
FROM gcr.io/distroless/base
COPY --from=build /app/app /app
CMD ["/app"]

CGO_ENABLED=0 = ไฟล์เดียวจบ (1.26 cgo เร็วขึ้น 30% แต่ถ้าไม่ใช้ C ปิดไปเลยเล็กสุด)

CI ขั้นต่ำ:

bash
go fmt ./... && go vet ./... && go test -race ./...

ดูอาการ production: GODEBUG=gctrace=1 ดู GC + pprof -http เปิด flame graph (1.26 default) หาแท่งกว้างสุดก่อนแก้

อ้างอิง: std slog net/http signal, Go 1.26 (cgo/GC/pprof), Go 1.27 release

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ภาคผนวก: ตัวอย่างเพิ่ม + Best Practices
ตัวอย่างเพิ่ม 1: log เป็น JSON (prod อ่านด้วยเครื่อง)
go
log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
}))
log.Info("server เริ่ม", "port", 8080)
ตัวอย่างเพิ่ม 2: health check ที่เช็ก DB จริง
go
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    if err := db.PingContext(r.Context()); err != nil {
        http.Error(w, "db down", http.StatusServiceUnavailable)
        return
    }
    w.Write([]byte("ok"))
})
ตัวอย่างเพิ่ม 3: graceful shutdown (ตายสวย)
go
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
srv := &http.Server{Addr: ":8080", Handler: mux}
go srv.ListenAndServe()
<-ctx.Done() // รอ Ctrl+C
srv.Shutdown(context.Background()) // รอ request ค้างเสร็จก่อนเลิก
Best practices เช็กลิสต์
  • log เป็น JSON + level เสมอ (prod อ่านด้วยเครื่อง ไม่ใช่อ่านด้วยตา)
  • ทุก service มี /healthz เช็ก dependency จริง (ping DB)
  • shutdown ต้อง graceful (drain) ไม่ใช่ kill ทิ้ง
  • config 3 ชั้น: default < env < flag; secret มาจาก env เท่านั้น
  • metric ขั้นต่ำ: request count + latency + error rate
  • Docker ใช้ distroless/scratch ไฟล์เล็ก attack surface น้อย
  • มี pprof พร้อมเปิดดูตอน production มีอาการ (อย่าไปงมตอนไฟไหม้)
แบบฝึกหัด
  • ใส่ /healthz + graceful ให้ mini project ของตัวเอง แล้วลอง Ctrl+C ดู
  • เปิด pprof หาฟังก์ชันกิน CPU สูงสุดในโค้ดตัวเอง