EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / Linux / ระบบเฝ้าระวังที่ยิงถี่เกิน จนล้มอุปกรณ์ทั้งฟาร์มเอง
Linux

ระบบเฝ้าระวังที่ยิงถี่เกิน จนล้มอุปกรณ์ทั้งฟาร์มเอง

โดย Ton-Or ·17 ต.ค. 2026·อ่าน 10 นาที
ระบบเฝ้าระวังที่ยิงถี่เกิน จนล้มอุปกรณ์ทั้งฟาร์มเอง

ระบบเฝ้าระวังที่เราเขียนเองมีสิทธิ์เป็นต้นเหตุของเหตุขัดข้องพอ ๆ กับสิ่งที่มันเฝ้าอยู่ และมันอันตรายกว่าตรงที่ไม่มีใครสงสัยมันเลย เพราะทุกคนคิดว่ามันเป็นฝ่ายมาช่วยดับไฟ ไม่ใช่ฝ่ายจุดไฟ

พอดีรับดูแลฟาร์มเกตเวย์ชุดหนึ่งต่อจากคนเดิม เป็นเครื่องโฮสต์ตัวเดียวที่มีอุปกรณ์เชื่อมต่อผ่านพอร์ตอนุกรมแบบยูเอสบีเสียบอยู่หลายตัว แต่ละตัวทำหน้าที่เป็นทางออกของงานคนละชุด อาการที่เรียกผมเข้าไปคือ "ช่วงสาย ๆ อุปกรณ์หลุดพร้อมกันหมดทุกตัว แล้วเดี๋ยวก็กลับมาเอง" เกิดวันละสองสามรอบ ทีมเดิมสรุปว่าฮับเสีย เปลี่ยนไปสองตัวแล้วยังเป็นเหมือนเดิม พอไล่ดูจริง ๆ ต้นเหตุไม่ได้อยู่ที่ฮาร์ดแวร์เลย มันคือตัวเฝ้าระวังที่ทีมเดิมเขียนไว้เอง

ชื่อโฮสต์ เลข IP และชื่อบริการในบทความเป็นค่าตัวอย่าง ส่วนคำสั่ง ข้อความจากเคอร์เนล และค่าที่แก้แล้วหาย เป็นของจริง

อาการ: อุปกรณ์ตัวเดียวค้าง แล้วล้มทั้งฟาร์ม

สิ่งแรกที่ต้องทำคือเลิกฟังคำสรุปของคนก่อนหน้า แล้วไปดูช่วงเวลาจริงว่าอะไรเกิดก่อนอะไร ผมดึงบันทึกของเคอร์เนลช่วงที่เกิดเหตุออกมาเรียงตามเวลา แล้วภาพก็ชัดขึ้นทันที

# ดูเฉพาะช่วงเหตุการณ์ ไม่ใช่ไล่ทั้งวัน
journalctl -k --since "2026-11-02 09:40" --until "2026-11-02 09:50" -o short-precise \
  | grep -Ei 'usb|tty|disconnect|reset'

สิ่งที่เห็นคืออุปกรณ์ตัวหนึ่งหลุดก่อนเพื่อนราวสี่สิบวินาที จากนั้นมีการรีเซ็ตพอร์ตซ้ำ ๆ ถี่มาก แล้วอีกสักพักตัวที่เหลือถึงเริ่มทยอยหลุดตาม สุดท้ายบัสทั้งชุดล้มพร้อมกัน

usb 1-1.3: USB disconnect, device number 14
usb 1-1.3: new full-speed USB device number 15 using ehci-pci
usb 1-1.3: device descriptor read/64, error -110
usb 1-1.3: new full-speed USB device number 16 using ehci-pci
usb 1-1-port3: Cannot enable. Maybe the USB cable is bad?
hub 1-1:1.0: cannot reset port 3 (err = -32)

ลำดับแบบนี้อ่านได้อย่างเดียวคือมีคนสั่งรีเซ็ตซ้ำตอนที่ตัวเดิมยังลงทะเบียนตัวเองไม่เสร็จ ไม่ใช่อาการของสายหลวมที่ปกติจะหลุด ๆ ติด ๆ แบบไม่มีจังหวะ อันนี้มีจังหวะชัดเจนมาก และจังหวะนั้นคือทุกสามสิบวินาทีพอดี

อาการที่มีจังหวะสม่ำเสมอ แปลว่ามีสคริปต์อยู่เบื้องหลัง

ฮาร์ดแวร์ที่เสียจริงมักจะพังแบบไม่มีแบบแผน แต่ถ้าเปิดบันทึกแล้วเห็นเหตุการณ์เดิมซ้ำทุก 30 วินาทีหรือทุก 1 นาทีเป๊ะ ๆ ให้ไปหาตัวที่รันตามรอบก่อนเสมอ ไม่ว่าจะเป็น cron ตัวจับเวลาของ systemd หรือลูปในบริการที่เราเขียนเอง

ตัดสมมติฐานฮาร์ดแวร์ให้จบก่อน

ถึงจะเดาได้แล้วว่าเป็นซอฟต์แวร์ ผมก็ยังตัดเรื่องฮาร์ดแวร์ให้จบก่อนอยู่ดี เพราะถ้าฮับจ่ายไฟไม่พอจริง ต่อให้แก้สคริปต์ดีแค่ไหนก็ยังพัง ตัวเลขที่ต้องดูคือกำลังไฟที่ฮับประกาศว่าจ่ายได้ เทียบกับที่อุปกรณ์ทุกตัวรวมกันขอ

lsusb -t                                   # ดูผังว่าตัวไหนห้อยอยู่ใต้ฮับตัวไหน
lsusb -v 2>/dev/null | grep -E 'MaxPower|bNumInterfaces|iProduct' | head -40

ผลที่ได้คือฮับตัวนั้นรายงานค่ากำลังไฟที่จ่ายได้เป็นศูนย์ ซึ่งแปลว่ามันเป็นฮับที่ดึงไฟจากพอร์ตต้นทางอย่างเดียว ไม่มีอะแดปเตอร์แยก ส่วนอุปกรณ์แต่ละตัวขอมาตัวละหลายร้อยมิลลิแอมป์ รวมแล้วเกินที่พอร์ตเดียวจะจ่ายไหวแน่นอน อันนี้เป็นปัญหาจริงที่ต้องแก้ แต่ยังไม่ใช่ต้นเหตุของอาการที่เกิดเป็นจังหวะ

สมมติฐานของทีมเดิม

ฮับเสีย เปลี่ยนฮับ

เปลี่ยนไปสองตัวแล้วอาการเหมือนเดิม เพราะซื้อรุ่นที่ดึงไฟจากพอร์ตต้นทางเหมือนกันทั้งสองครั้ง และไม่ได้แตะต้นเหตุจริงที่อยู่ในสคริปต์เลย เสียทั้งเงินและเวลาไปสองสัปดาห์

ที่ต้องทำจริง

แก้ทั้งสองชั้น

เปลี่ยนเป็นฮับที่มีไฟเลี้ยงแยกเพื่อให้กำลังไฟพอจริง แล้วแก้ลูปที่กระแทกอุปกรณ์ซ้ำ ซึ่งเป็นตัวที่ทำให้อาการลุกลามจากตัวเดียวไปทั้งบัส สองข้อนี้แยกกันคนละเรื่อง ต้องทำทั้งคู่

ทำตัวจับว่าใครแตะอุปกรณ์บ้าง

พอสงสัยว่ามีคนสั่งรีเซ็ต คำถามถัดมาคือใคร บนเครื่องที่รับมาต่อจากคนอื่นมักมีของรันอยู่มากกว่าที่เอกสารบอก ผมเลยเขียนตัวจับง่าย ๆ ถ่ายภาพนิ่งทุกห้าวินาทีว่าตอนนั้นมีโปรเซสไหนเปิดไฟล์อุปกรณ์อยู่บ้าง และรายชื่ออุปกรณ์ที่ระบบมองเห็นเปลี่ยนไปหรือยัง

#!/usr/bin/env python3
"""flap-detector: ถ่ายภาพนิ่งทุก 5 วิ ว่ามีใครแตะอุปกรณ์ และรายชื่อเปลี่ยนไหม"""
import glob, os, subprocess, time, json, datetime

def holders(path):
    """คืนรายชื่อโปรเซสที่เปิดไฟล์อุปกรณ์นี้ค้างไว้"""
    p = subprocess.run(["fuser", "-v", path], capture_output=True, text=True)
    out = []
    for line in (p.stderr or "").splitlines()[1:]:
        f = line.split()
        if len(f) >= 3:
            out.append("%s/%s" % (f[1], f[-1]))      # pid/ชื่อคำสั่ง
    return out

prev = None
while True:
    devs = sorted(glob.glob("/dev/ttyUSB*"))
    snap = {d: holders(d) for d in devs}
    if snap != prev:
        rec = dict(ts=datetime.datetime.now().isoformat(timespec="seconds"),
                   count=len(devs), snap=snap)
        print(json.dumps(rec, ensure_ascii=False), flush=True)
        prev = snap
    time.sleep(5)

รันทิ้งไว้ครึ่งชั่วโมงก็ได้คำตอบ มีสองฝ่ายที่แตะอุปกรณ์ชุดเดียวกัน ฝ่ายแรกคือบริการเฝ้าระวังที่รันตามรอบ ฝ่ายที่สองคือปุ่มบนหน้าเว็บจัดการที่ทีมเดิมทำไว้ให้กดสั่งกู้อุปกรณ์ด้วยมือ และทั้งสองฝ่ายไม่รู้จักกันเลย ไม่มีล็อกอะไรกั้นไว้ระหว่างกัน

# ยืนยันอีกทางว่าใครถืออุปกรณ์อยู่ตอนนี้
sudo lsof /dev/ttyUSB* 2>/dev/null | awk 'NR==1 || $0 ~ /ttyUSB/'
systemctl list-timers --all | grep -i gw

ต้นเหตุ: ลูปที่ไม่มีคูลดาวน์ และไม่มีล็อก

พอไปเปิดสคริปต์เฝ้าระวังตัวเดิมดู ตรรกะมันตรงไปตรงมามาก และนั่นแหละคือปัญหา ทุกสามสิบวินาที วนอุปกรณ์ทุกตัว ตัวไหน ping ปลายทางไม่ผ่านก็สั่งกู้ทันที ไม่มีการนับว่าล้มเหลวติดกันกี่ครั้ง ไม่มีการจำว่าเพิ่งกู้ตัวนี้ไปเมื่อไหร่ และไม่มีการจำกัดว่ากู้พร้อมกันได้กี่ตัว

# ตรรกะของตัวเดิม ย่อมาแต่ใจความครบ
while True:
    for dev in devices:
        if not ping(dev["ip"], timeout=2):
            usb_reset(dev["bus_path"])     # สั่งทันที ไม่มีเงื่อนไขอะไรเลย
    time.sleep(30)

ปัญหาคือการกู้ด้วยการรีเซ็ตพอร์ตใช้เวลานานกว่าสามสิบวินาทีเสมอ อุปกรณ์ต้องหลุดออก ลงทะเบียนใหม่ ขอค่าเชื่อมต่อปลายทางใหม่ รวมแล้วเกินหนึ่งนาที พอรอบถัดไปมาถึงตอนที่ตัวนั้นยังลงทะเบียนไม่เสร็จ ping ก็ยังไม่ผ่านอยู่แล้ว สคริปต์เลยสั่งรีเซ็ตซ้ำ วนแบบนี้ไปเรื่อย ๆ ตัวที่ค้างไม่มีวันกลับมา และการรีเซ็ตถี่ ๆ บนบัสเดียวกันก็ลากตัวอื่นล้มตามไปด้วย

ลูปรอบละ 30 วินาที เทียบกับการกู้ที่ใช้เวลานานกว่า0 s30 s60 s90 s120 sลูปเฝ้าสั่งรีเซ็ตสั่งซ้ำสั่งซ้ำสั่งซ้ำรีเซ็ต 1 ยังลงทะเบียนไม่เสร็จรีเซ็ต 2 ถูกสั่งทับกลางทางรีเซ็ต 3 วนซ้ำต่อไปเรื่อย ๆอุปกรณ์ที่ค้างการกู้ใช้เวลานานกว่า 30 วินาทีเสมอ รอบถัดไปมาถึงตอนตัวเดิมยังลงทะเบียนไม่เสร็จ ping ก็ยังไม่ผ่านผลคือบัสรวนและตัวอื่นหลุดตามไปด้วย
ลูปสั่งรีเซ็ตทุก 30 วินาที ทั้งที่การกู้แต่ละครั้งใช้เวลาราว 60 วินาที รอบถัดไปเลยมาสั่งซ้ำตอนตัวเดิมยังไม่ลงทะเบียนเสร็จ
วงจรที่ทำให้ตัวเดียวล้มทั้งฟาร์มตัวหนึ่งค้างping ไม่ผ่าน→สั่งรีเซ็ตใช้เวลา 60 วิ→รอบใหม่มาที่ 30 วิยังไม่ผ่าน สั่งซ้ำ→บัสรวนตัวอื่นหลุดตาม→ทุกตัวเข้าเงื่อนไขสั่งกู้พร้อมกันหมด

ที่น่ากลัวของเคสนี้คือ ยิ่งระบบพัง ตัวเฝ้าระวังยิ่งทำงานหนักขึ้น เพราะจำนวนอุปกรณ์ที่เข้าเงื่อนไขเพิ่มขึ้นเรื่อย ๆ มันเลยไม่ใช่แค่ไม่ช่วย แต่เป็นตัวเร่ง และไม่มีใครสงสัยมันเลยตลอดสองสัปดาห์ที่ผ่านมา เพราะทุกคนมองว่ามันเป็นฝ่ายมาช่วย

เขียนการกู้ใหม่: ทีละตัว มีคูลดาวน์ มีเพดาน

ค่าที่แก้แล้วอาการหายไปเลยมีสี่ตัว ผมเขียนใหม่ทั้งก้อนแต่ตรรกะหลักสั้นมาก หัวใจคือก่อนจะลงมือกู้ ต้องผ่านเงื่อนไขสี่ข้อให้ครบ

import json, os, time, subprocess

STATE = "/var/lib/gw-watchdog/state.json"
INTERVAL   = 60      # รอบละ 60 วิ ยาวกว่าที่การกู้ใช้เวลาจริง
NEED_FAIL  = 3       # ต้องล้มติดกัน 3 รอบก่อน ไม่ใช่พลาดครั้งเดียวแล้วลุย
COOLDOWN   = 300     # กู้ตัวเดิมซ้ำได้เร็วสุดทุก 5 นาที
MAX_PER_HR = 6       # เพดานต่อตัวต่อชั่วโมง เกินแล้วหยุดและแจ้งคน

def load():
    try:
        return json.load(open(STATE))
    except Exception:
        return {}

def save(s):
    os.makedirs(os.path.dirname(STATE), exist_ok=True)
    json.dump(s, open(STATE, "w"))

def should_recover(dev, st, now):
    d = st.setdefault(dev, {"fail": 0, "last": 0, "hits": []})
    if d["fail"] < NEED_FAIL:
        return False, "ยังล้มไม่ครบ %d รอบ" % NEED_FAIL
    if now - d["last"] < COOLDOWN:
        return False, "อยู่ในคูลดาวน์อีก %ds" % int(COOLDOWN - (now - d["last"]))
    d["hits"] = [t for t in d["hits"] if now - t < 3600]
    if len(d["hits"]) >= MAX_PER_HR:
        return False, "ชนเพดาน %d ครั้ง/ชม. ต้องให้คนเข้ามาดู" % MAX_PER_HR
    return True, "ok"
def tick(devices):
    st, now = load(), time.time()
    recovered = 0
    for dev in devices:
        d = st.setdefault(dev["id"], {"fail": 0, "last": 0, "hits": []})
        if ping(dev["ip"], timeout=2):
            d["fail"] = 0
            continue
        d["fail"] += 1
        ok, why = should_recover(dev["id"], st, now)
        if not ok:
            log("skip %s: %s" % (dev["id"], why))
            continue
        if recovered >= 1:                      # ทีละตัวต่อรอบเท่านั้น
            log("skip %s: รอบนี้กู้ไปแล้วหนึ่งตัว" % dev["id"])
            continue
        log("recover %s (fail=%d)" % (dev["id"], d["fail"]))
        usb_reset(dev["bus_path"])
        d["last"] = now
        d["hits"].append(now)
        d["fail"] = 0                            # เริ่มนับใหม่ ให้โอกาสมันฟื้น
        recovered += 1
    save(st)

เงื่อนไขข้อที่ผมคิดว่าสำคัญที่สุดคือเพดานต่อชั่วโมง เพราะมันเปลี่ยนพฤติกรรมของระบบตอนเจอปัญหาที่แก้เองไม่ได้ ระบบเดิมจะพยายามไปเรื่อย ๆ ไม่มีวันหยุด ระบบใหม่จะพยายามหกครั้งแล้วยอมแพ้พร้อมส่งเสียงเรียกคน ซึ่งเป็นพฤติกรรมที่ถูกต้องกว่า อุปกรณ์ที่กู้หกครั้งใน 1 ชั่วโมงแล้วยังไม่กลับมา มันต้องการมือคน ไม่ใช่ครั้งที่เจ็ด

ปิดทางฝั่งเว็บด้วยล็อกตัวเดียวกัน

แก้ฝั่งบริการอย่างเดียวไม่พอ เพราะปุ่มบนหน้าเว็บยังยิงคำสั่งเดิมได้อยู่ และเจ้าหน้าที่ที่เห็นว่าอุปกรณ์หลุดก็จะกดรัว ๆ ตามสัญชาตญาณ ผมเลยบังคับให้ทุกเส้นทางที่จะแตะอุปกรณ์ต้องผ่านล็อกไฟล์ตัวเดียวกัน ใครถืออยู่คนอื่นต้องรอหรือถอย

#!/bin/bash
# ตัวห่อเดียวที่ได้รับอนุญาตให้สั่งกู้ ทั้งบริการและหน้าเว็บต้องเรียกผ่านตัวนี้
exec 9>/var/lock/gw-recover.lock
if ! flock -n 9; then
  echo '{"ok":false,"err":"มีการกู้อุปกรณ์อื่นอยู่ กรุณารอสักครู่"}'
  exit 75
fi
/usr/local/bin/gw-recover.py "$@"
[Unit]
Description=Gateway watchdog
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/gw-watchdog.py
Restart=on-failure
RestartSec=30
# กันตัวเองพังแล้ววนรีสตาร์ตรัวจนกลายเป็นลูปกระแทกอีกแบบ
StartLimitIntervalSec=600
StartLimitBurst=5

[Install]
WantedBy=multi-user.target
ทุกเส้นทางที่แตะฮาร์ดแวร์ต้องผ่านประตูเดียวกัน

ไม่ว่าจะเป็นบริการตามรอบ ปุ่มบนหน้าเว็บ สคริปต์ที่ช่างรันด้วยมือ หรืองานตามเวลาที่ตั้งไว้นานแล้วจนลืม ทั้งหมดต้องเรียกผ่านตัวห่อตัวเดียวที่ถือล็อก ถ้ามีทางไหนเหลืออยู่ที่เลี่ยงล็อกได้ วันหนึ่งมันจะถูกใช้ตอนที่เราไม่อยู่ แล้วเราจะกลับมาไล่หาสาเหตุเดิมอีกรอบ

ทำให้เห็นว่ามันทำอะไรไปบ้าง ก่อนจะเชื่อมัน

ของที่ขาดไปทั้งหมดในระบบเดิมคือร่องรอย มันสั่งกู้อุปกรณ์วันละหลายสิบครั้งโดยไม่มีใครรู้ ระบบใหม่ผมบังคับให้ทุกการตัดสินใจมีบรรทัดของตัวเอง ทั้งตอนที่ลงมือและตอนที่ตัดสินใจไม่ลงมือ บรรทัดที่บอกว่า "ข้ามเพราะอยู่ในคูลดาวน์" มีค่ามากกว่าบรรทัดที่บอกว่ากู้สำเร็จอีก เพราะมันคือหลักฐานว่ากลไกป้องกันทำงานอยู่จริง

journalctl -u gw-watchdog --since today | grep -c 'recover '
journalctl -u gw-watchdog --since today | grep 'skip ' | awk '{print $NF}' | sort | uniq -c
30 วิรอบเดิมที่สั้นเกินไป
60 วิรอบใหม่หลังแก้
5 นาทีคูลดาวน์ต่ออุปกรณ์
1 ตัวกู้ได้ต่อรอบ

หลังแก้แล้วจำนวนครั้งที่สั่งกู้ต่อวันลดจากหลักหลายสิบเหลือหลักหน่วย และเหตุการณ์อุปกรณ์ล้มพร้อมกันทั้งชุดหายไปเลย ที่เหลือคือการหลุดของตัวเดี่ยว ๆ ที่กู้รอบเดียวก็กลับมา ซึ่งเป็นอาการปกติของอุปกรณ์ประเภทนี้อยู่แล้ว

กติกาที่ผมยึดกับ automation ที่แตะฮาร์ดแวร์

  1. รอบของลูปต้องยาวกว่าเวลาที่การกู้ใช้จริงเสมอ ถ้ากู้ใช้ 1 นาที ห้ามตั้งรอบไว้ 30 วินาที เพราะรอบถัดไปจะไปกระแทกของที่ยังทำงานค้างอยู่
  2. ต้องล้มเหลวติดกันหลายรอบก่อนถึงจะลงมือ พลาดครั้งเดียวไม่ใช่เหตุการณ์ขัดข้อง
  3. มีคูลดาวน์ต่ออุปกรณ์ และกู้ได้ทีละตัวต่อรอบ อย่าให้เหตุการณ์เดียวกลายเป็นการสั่งพร้อมกันทั้งฟาร์ม
  4. มีเพดานต่อชั่วโมง ชนเพดานแล้วให้หยุดและเรียกคน ไม่ใช่พยายามต่อไปเรื่อย ๆ
  5. ทุกเส้นทางที่แตะอุปกรณ์ต้องผ่านตัวห่อที่ถือล็อกร่วมกัน รวมถึงปุ่มบนหน้าเว็บและสคริปต์ที่รันด้วยมือ
  6. บันทึกทั้งตอนลงมือและตอนตัดสินใจไม่ลงมือ พร้อมเหตุผล ไม่งั้นเราจะไม่มีทางรู้ว่ามันทำอะไรไปบ้าง
  7. ตัดสมมติฐานฮาร์ดแวร์ให้จบคู่กันไปด้วย ไฟไม่พอกับลูปกระแทกเป็นคนละเรื่อง แก้ข้อเดียวไม่หาย

ส่วนตัวผมคิดว่าเครื่องมือเฝ้าระวังที่มีสิทธิ์สั่งการแก้ไขได้เอง ต้องถูกออกแบบด้วยความระมัดระวังมากกว่าตัวที่ได้แค่ดูแล้วแจ้ง เพราะตัวที่แจ้งอย่างเดียวพังมากที่สุดคือแจ้งผิด แต่ตัวที่สั่งการได้พังแล้วล้มระบบจริง กฎที่ผมใช้คือถ้าจะให้มันแก้เองได้ ต้องออกแบบให้มันยอมแพ้เป็นด้วย และต้องยอมแพ้เร็วกว่าที่จะสร้างความเสียหาย ใครจะเอาโครงนี้ไปใช้กับงานกู้บริการอื่นที่ไม่ใช่ฮาร์ดแวร์ เช่นรีสตาร์ตบริการที่ค้าง เงื่อนไขสี่ข้อชุดเดียวกันใช้ได้หมด เปลี่ยนแค่คำสั่งที่ใช้กู้

ที่ยังค้างในแผนของผมคือเก็บสถิติว่าอุปกรณ์ตัวไหนต้องกู้บ่อยกว่าเพื่อนในรอบสามสิบวัน เพื่อจะได้รู้ว่าตัวไหนใกล้ต้องเปลี่ยนจริง ไม่ใช่รอให้มันตายคาที่ กับลองย้ายการกู้ไปทำที่ระดับพอร์ตของฮับที่สั่งตัดไฟได้ทีละช่อง ซึ่งน่าจะสะอาดกว่าการรีเซ็ตที่ระดับบัส ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

อยากให้เราทำให้ไหม?

ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า

คุยเรื่องงานกับเรา

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

ยังไม่มีความคิดเห็น — เป็นคนแรกเลย