EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / Web / composer update บน PHP 8.4 แล้ว worker กับ cron ที่ใช้ PHP เก่าพัง
Web

composer update บน PHP 8.4 แล้ว worker กับ cron ที่ใช้ PHP เก่าพัง

โดย Ton-Or ·28 ต.ค. 2026·อ่าน 10 นาที
composer update บน PHP 8.4 แล้ว worker กับ cron ที่ใช้ PHP เก่าพัง

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

สรุปใน 1 นาที

อาการ/ปัญหา
composer update บน PHP 8.4 ทำให้ worker, cron และ subdomain ที่ใช้ PHP เก่าล้มทีละตัว
ใครควรอ่าน
คนที่ vendor เดียวถูกเปิดโดย PHP หลายรุ่นบนเซิร์ฟเวอร์เดียวกัน
ทางแก้แบบสั้น
ไล่หา runner ทั้งหมดและ PHP ที่แต่ละตัวใช้ก่อนแตะ composer แล้วเลือกรุ่นที่ตรงกับ runner ต่ำสุด จากนั้นตรวจหลังอัปเดต
ข้ามไปขั้นตอน

พอดีวันที่ผมไล่อัปเดตแพ็กเกจที่ composer audit เตือนให้แอป Laravel ราวหนึ่งโหลดบนโฮสต์ DirectAdmin เครื่องเดียว ผมรัน composer ด้วย PHP 8.4 แล้วเช็กเว็บหลักของทุกแอปได้ 200 หมด เลยบอกตัวเองว่าเสร็จ แต่หลังจากนั้นปรากฏว่า queue worker วนรีสตาร์ตอยู่เกือบชั่วโมงโดยไม่มีใครรู้ cron สองตัวล้ม และ subdomain สองตัวของแอปเดียวกันตอบ 500 ต้นเหตุไม่ใช่แพ็กเกจเสียและไม่ใช่ extension หาย แต่เป็นไฟล์เล็ก ๆ ไฟล์เดียวที่ Composer เขียนไว้ให้เองโดยที่ผมไม่ได้สั่ง

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

อาการ: เว็บหลักผ่าน แต่รอบข้างล้มทีละตัว

เครื่องนี้เป็นโฮสต์ร่วมที่รัน PHP หลายรุ่นคู่กัน โดเมนและ subdomain แต่ละตัวมี PHP-FPM pool กับรุ่น PHP เป็นของตัวเอง ขณะที่คำสั่ง php บนบรรทัดคำสั่งเป็น symlink ที่ชี้ไป 8.2 ซึ่งเก่ากว่า งาน cron จำนวนมากกับ systemd unit ของ worker ตัวหนึ่งเรียกผ่าน symlink ตัวนี้อยู่ ตอนวางแผนอัปเดตผมไม่ได้เอามันมานับรวมเป็นทางเข้าของแอปเลย

สิ่งที่เห็นหลังอัปเดตเรียงตามเวลาที่เจอ

  1. เว็บหลักของทุกแอปตอบ 200 ผ่านด่านที่ผมตั้งไว้
  2. queue worker ของแอปหนึ่งที่ ExecStart ชี้ไป symlink ตัวกลาง (8.2) ล้มแล้ว systemd สั่งให้ขึ้นใหม่ ล้มอีก วนแบบนี้อยู่ราวหนึ่งชั่วโมงกว่าจะมีคนสังเกต
  3. cron schedule:run กับ cron ที่ใช้เผยแพร่บทความตามเวลา ซึ่งเรียก php ตัวกลางเหมือนกัน ล้มทั้งคู่
  4. subdomain สองตัวของแอปเดียวกัน ตัวหนึ่งตั้งไว้ที่ 8.2 อีกตัวตั้งไว้ที่ 8.3 ใน DirectAdmin ตอบ 500 จนผมย้ายไป 8.4 ตัวหนึ่งใช้เวลาราว 40 นาที
ราว 12แอป Laravel ที่อัปเดตรอบนี้
1 unitqueue worker ที่วนรีสตาร์ต
2 cronที่ล้มจาก PHP ตัวกลาง
2 ตัวsubdomain ที่ตอบ 500
ราว 1 ชม.worker ล้มโดยไม่มีใครรู้
ราว 40 นาทีsubdomain ที่นานที่สุด

เดาผิดก่อน: นึกว่าแพ็กเกจพัง

ผมเดาแรกสุดว่าแพ็กเกจที่เพิ่งอัปเดตตัวใดตัวหนึ่งพัง หรือไม่ก็ extension ที่แอปต้องใช้หายไปจากรุ่นที่เก่ากว่า ถ้าไปไล่ log ของแอปก็ไม่มีอะไรให้เจอ เพราะ autoload ล้มก่อนที่แอปจะเริ่มทำงาน คำตอบจริงอยู่ที่ journal ของ worker กับตัวหน้า error ที่ FPM ส่งกลับ

$ journalctl -u app-a-queue.service -n 6 --no-pager
app-a-queue.service: Main process exited, code=exited, status=255/EXCEPTION
Composer detected issues in your platform:
Your Composer dependencies require a PHP version ">= 8.4". You are running 8.2.x.
PHP Fatal error: Uncaught RuntimeException: Composer detected issues in your platform
  in /home/webuser/app-a/vendor/composer/platform_check.php
app-a-queue.service: Scheduled restart job, restart counter is at <N>.

ข้อความบอกตรง ๆ ว่าต้องการ PHP 8.4 ขึ้นไปแต่รันอยู่ที่ 8.2 ถ้าอ่านดี ๆ นี่ไม่ใช่บั๊ก แต่เป็น Composer ทำหน้าที่ป้องกันเรา ผมเสียเวลาไปกับการไล่ดูแพ็กเกจเพราะไม่ได้อ่านบรรทัดนี้ก่อน

ไฟล์ platform_check.php ทำงานยังไง

ทุกครั้งที่ composer install หรือ composer update Composer รวมเงื่อนไข php ของทุกแพ็กเกจใน lock file แล้วเขียนเป็นไฟล์ vendor/composer/platform_check.php ให้ ไฟล์นี้ถูกเรียกจาก autoloader ทุกครั้งที่มีการโหลด vendor ไม่ว่าจะผ่านเว็บ CLI หรือ worker

<?php
// vendor/composer/platform_check.php  (@generated by Composer)
// ตัวเลขข้างล่างมาจากแพ็กเกจที่ต้องการ PHP สูงสุดใน lock file
$issues = array();

if (!(PHP_VERSION_ID >= 80400)) {
    $issues[] = 'Your Composer dependencies require a PHP version ">= 8.4". '
              . 'You are running ' . PHP_VERSION . '.';
}

if ($issues) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');   // ฝั่งเว็บ: 500
    }
    if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
        fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL);  // ฝั่ง CLI: เขียน STDERR
    }
    throw new \RuntimeException('Composer detected issues in your platform: ' . implode(' ', $issues));
}

จุดที่ผมไม่ได้คิดมาก่อนคือเกณฑ์นี้ไม่ได้ดูว่าเครื่องเรามี PHP รุ่นไหนบ้าง ผมรัน update ด้วย 8.4 Composer เลยเลือกแพ็กเกจที่ยกรุ่นแล้วบางตัวต้องการ 8.4 ขึ้นไป (ถ้ารันด้วยรุ่นต่ำกว่า มันต้องเลือกรุ่นแพ็กเกจที่รองรับรุ่นนั้นแทน) เกณฑ์ในไฟล์เลยขยับขึ้นตาม และไล่ทุกตัวที่ต่ำกว่า 8.4 ออกไป ไม่ว่าจะเปิด vendor ก้อนนี้ทางไหน

สายพานสี่เลนในห้องเซิร์ฟเวอร์ สามเลนถูกแผงกั้นสีแดงกั้นกล่องรุ่นเก่า มีเลนเดียวที่กล่องรุ่นใหม่ผ่านไปได้
สี่ทางเข้า (เว็บหลัก, subdomain, cron, worker) ใช้ vendor เดียวกัน แต่แผงกั้นเกณฑ์รุ่นไล่กล่องรุ่นเก่าออกไปสามเลน เว็บหลักที่ผมเช็กคือเลนเดียวที่ผ่าน
vendor/ ก้อนเดียว แต่ PHP ที่เปิดมันมีหลายตัวcomposer updateรันด้วย PHP 8.4platform_check.phpPHP_VERSION_ID>= 80400เขียนใหม่ตอน updateFPM โดเมนหลัก (8.4)ผ่าน ตอบ 200FPM subdomain (8.2)ตอบ 500 ทุก requestcron schedule:run (8.2)ผ่าน symlink php ล้มทุกรอบsystemd worker (8.2)ExecStart=symlink วนรีสตาร์ตคนละ binary แต่ autoload ไฟล์เดียวกัน ตัวที่ต่ำกว่าเกณฑ์ล้มทันทีตั้งแต่บรรทัดแรก
ไฟล์เดียวใน vendor ถูกเปิดโดย PHP สี่ตัว มีตัวเดียวที่ผ่านเกณฑ์ เว็บหลักที่เช็กไว้คือตัวนั้นพอดี
เช็กเว็บหลักผ่าน ไม่ได้แปลว่าแอปผ่าน

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

พิสูจน์ว่าแต่ละ runner ใช้ PHP ตัวไหน

วิธีพิสูจน์ที่เร็วที่สุดคือถาม binary ตัวที่ถูกเรียกจริง ไม่ใช่ถามว่าเครื่องนี้ติดตั้งอะไรไว้ เพราะเครื่องเดียวกันมี PHP หลายรุ่นพร้อมกัน

# 1) worker: ดู ExecStart แล้วถาม binary ตัวนั้นตรง ๆ
systemctl cat app-a-queue.service | grep ExecStart
/usr/local/bin/php -v | head -1

# 2) cron: ดูว่าบรรทัดไหนเรียก php ตัวไหน
crontab -l -u webuser | grep app-a

# 3) symlink ตัวกลางชี้ไปไหน
readlink -f /usr/local/bin/php

# 4) subdomain: ดูรุ่นที่ DirectAdmin ตั้งให้ แล้วเทียบกับเกณฑ์ใน platform_check.php
grep PHP_VERSION_ID /home/webuser/app-a/vendor/composer/platform_check.php

ผลที่ได้ตรงกับอาการหมด ExecStart กับ cron ทั้งสองบรรทัดชี้ symlink ตัวกลางที่เป็น 8.2 ส่วนสอง subdomain ถูกตั้งแยกไว้ที่ 8.2 กับ 8.3 ต่างจากโดเมนหลักที่เป็น 8.4 ทุกตัวต่ำกว่าเกณฑ์ที่ไฟล์เขียนไว้

เรื่อง FPM กับ CLI ใช้ binary คนละตัวแล้วให้ผลต่างกัน ผมเคยเขียนไว้อีกกรณีหนึ่งใน tinker ส่งเมลผ่าน แต่เว็บส่งไม่ออก ซึ่งสาเหตุคนละอย่าง (นั่นคือ proc_open ที่ปิดอยู่ใน FPM) แต่บทเรียนแนวเดียวกัน คือตัวที่ผ่านการทดสอบอาจไม่ใช่ตัวที่รันงานจริง และกรณีนี้ก็ไม่ใช่เรื่องแคชค้างอย่างที่เล่าไว้ใน deploy แล้วเว็บไม่อัปเดต — ไล่แคชห้าชั้น เพราะข้อความ error ชัดว่าเป็นเรื่องรุ่นของ PHP ล้วน ๆ

ไล่หา runner ทั้งหมดก่อนแตะ composer

บทเรียนข้อแรกคือต้องมีรายชื่อ runner ของแอปแต่ละตัวก่อนรัน composer ทุกครั้ง ผมเขียนสคริปต์สั้น ๆ ไล่หา path ของแอปในที่ที่งานชอบซ่อนอยู่

#!/bin/bash
# list_runners.sh <app_dir>  — อ่านอย่างเดียว ไม่แก้อะไรทั้งนั้น
APP=${1:?app dir}

echo "== cron ที่เรียกแอปนี้"
grep -l "$APP" /var/spool/cron/crontabs/* 2>/dev/null

echo "== systemd unit ที่ชี้แอปนี้"
grep -l "$APP" /etc/systemd/system/*.service 2>/dev/null

echo "== ไฟล์ override ของ DirectAdmin (subdomain ที่ชี้ docroot นี้)"
grep -rl "$APP" /usr/local/directadmin/data/users/*/domains/ 2>/dev/null | grep -i override

ข้อที่สามเป็นของที่ผมเกือบพลาด เพราะใน DirectAdmin รุ่น PHP ของ subdomain ไม่ได้อยู่ในหน้าตั้งค่าของโดเมนหลัก มันอยู่ในไฟล์ docroot.override ที่วางข้างไฟล์ conf ของโดเมน ถ้าไม่ค้นเลยจะไม่เห็นว่า subdomain ตัวไหนเปิดโค้ดชุดเดียวกับเว็บหลักอยู่

ที่ทำรอบนี้

เช็กอย่างเดียว

  • อัปเดตด้วย PHP 8.4
  • เทียบ HTTP ของ URL หลักก่อนหลัง
  • cron, worker, subdomain ไม่อยู่ในรายการ
  • รู้ว่าพังจากคนที่เห็นก่อน
ที่ทำต่อไป

ไล่ runner ก่อน

  • list_runners.sh ทุกแอปก่อนรัน
  • เลือก PHP ตามตัวต่ำสุดในกลุ่ม
  • เช็ก HTTP ทุก URL และ journal ของ worker
  • มีสัญญาณเตือนถ้า worker รีสตาร์ตซ้ำ

สูตรที่ผมใช้: เลือก PHP ให้ตรงกับ runner ที่ต่ำสุด

พอมีรายชื่อ runner แล้วมีสองทางให้เลือก ทางแรก รัน composer ด้วย PHP ที่ต่ำสุดในกลุ่ม ซึ่งทำให้ Composer เลือกแพ็กเกจที่รองรับรุ่นนั้นและเขียนเกณฑ์ใน platform_check.php ให้ต่ำลงตาม ทางที่สอง ย้ายทุก runner ไปรุ่นใหม่ก่อน แล้วค่อยรัน composer ด้วยรุ่นใหม่ สคริปต์แพตช์ของผมรับตัวแปร PHP เอาไว้ให้ใช้ทางแรกได้เลย

# ทางแรก: แอปนี้มี runner ต่ำสุดเป็น 8.3 -> รัน composer ด้วย 8.3
PHP=/usr/local/php83/bin/php bash laravel_composer_patch.sh apps.txt

# ทางที่สอง: ย้ายทุกตัวไป 8.4 ก่อน โดยชี้ binary เต็ม path ไม่ใช้ symlink กลาง
# crontab ของ webuser
* * * * * cd /home/webuser/app-a && /usr/local/php84/bin/php artisan schedule:run >> /dev/null 2>&1
# /etc/systemd/system/app-a-queue.service (เฉพาะส่วนสำคัญ)
[Service]
User=webuser
WorkingDirectory=/home/webuser/app-a
ExecStart=/usr/local/php84/bin/php artisan queue:work --sleep=3 --tries=3
Restart=always
RestartSec=5

ส่วนตัวผมเลือกทางที่สองเมื่อมีเวลา เพราะการชี้ path เต็มของรุ่นที่ต้องการทำให้ runner ไม่ขึ้นกับ symlink ตัวกลางอีก ครั้งหน้าใครเปลี่ยน symlink ก็ไม่กระทบแอปนี้ แต่ถ้าวันนั้นต้องแพตช์ช่องโหว่ให้จบก่อน ทางแรกปลอดภัยกว่า ตามสะดวกครับ ส่วนจะเลือกอันไหนก็ตาม ห้ามเปลี่ยน symlink php ตัวกลางเอง

อย่าแก้ด้วยการเปลี่ยน symlink ตัวกลาง

symlink /usr/local/bin/php ถูกใช้โดย cron ของผู้ใช้อื่นบนเครื่องเดียวกัน เปลี่ยนมันเพื่อแก้แอปของเราแอปเดียว คือไปสร้างปัญหาแบบเดียวกันให้คนอื่นแทน

platform-check false แค่ปิดเสียง

composer config platform-check false ทำให้ข้อความหายไป แต่แพ็กเกจอาจใช้ไวยากรณ์ของ 8.4 จริง ซึ่ง PHP 8.2 อ่านไม่ออก แอปจะล้มกลางทางด้วย parse error แทน ซึ่งหายากกว่าเดิมมาก ผมไม่แนะนำให้ใช้เป็นทางแก้

ขั้นตอนหลังอัปเดต และสัญญาณเตือน

สคริปต์แพตช์ที่ผมใช้ทำสิ่งต่อไปนี้ไว้แล้วตั้งแต่ก่อนเกิดเหตุ คือสำรอง vendor กับ composer.json และ lock ต่อแอป อัปเดตเฉพาะแพ็กเกจที่ audit เตือนภายในช่วงที่ composer.json อนุญาต reload php-fpm เช็ก HTTP และคืนของเดิมเองถ้าหน้าเปลี่ยนจากต่ำกว่า 500 เป็น 500 ขึ้นไป ช่องโหว่คือมันเช็กแค่ URL หลักของแอป ขั้นตอนที่ผมจะใช้ต่อจากนี้เลยเป็นแบบนี้

  1. รัน list_runners.sh ของทุกแอปก่อน แล้วจดว่าแต่ละตัวใช้ PHP รุ่นไหน
  2. เลือก PHP สำหรับ composer ให้เท่ากับรุ่นต่ำสุดในกลุ่ม หรือย้าย runner ทุกตัวให้เสร็จก่อน
  3. สำรอง vendor กับ composer.json และ lock ของแอปนั้น แล้วค่อยรัน update เฉพาะแพ็กเกจที่ audit เตือน
  4. เช็ก HTTP ของโดเมนหลักและ subdomain ทุกตัวที่ชี้โค้ดชุดเดียวกัน
  5. ดู journalctl ของ worker อย่างน้อยสองสามนาทีหลังรีสตาร์ต และรอรอบ cron หนึ่งรอบ
  6. ถ้าตัวใดตัวหนึ่งล้ม ให้คืน vendor จากที่สำรองไว้ แล้วกลับไปข้อ 2
ขั้นตอนรอบ composer update ทุกครั้ง1 ไล่หา runnercrontab ทุก userunit ใน systemdoverride ของ subdomainที่ชี้ path ของแอป2 เลือก PHPใช้รุ่นต่ำสุดในกลุ่ม runner หรือย้ายทุกตัวไป 8.4ให้ครบก่อน3 composer updateสำรอง vendor กับcomposer.json/lockอัปเฉพาะแพ็กเกจที่ audit เตือน4 ตรวจทุกทางHTTP โดเมนหลักและทุก subdomainjournalctl ของ workerล้มแล้วคืนของเดิมช่องโหว่ของสคริปต์เดิม: เช็กแค่ URL เว็บหลักทางที่เหลืออีกสาม (subdomain, cron, worker) ไม่มีใครถามข้อ 1 กับข้อ 2 ต้องทำก่อนข้อ 3 ถ้าสลับ ความเสียหายเกิดก่อนที่จะรู้ว่ามี runner อยู่
ขั้นที่ 1 กับ 2 คือส่วนที่ขาดไป สคริปต์เดิมมีแค่ขั้น 3 กับการเช็ก URL เดียวในขั้น 4

ขั้นเช็ก HTTP ที่ควรเป็นคือวนทุก URL ที่เกี่ยวกับแอปนั้น ไม่ใช่แค่ตัวเดียว

for u in https://example.com/ https://sub1.example.com/ https://sub2.example.com/; do
  printf '%s %s\n' "$(curl -sk -o /dev/null -w '%{http_code}' --max-time 20 "$u")" "$u"
done

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

n=$(systemctl show -p NRestarts --value app-a-queue.service)
[ "${n:-0}" -gt 3 ] && echo "[WARN] app-a-queue restarted $n times"
ภาพก่อนหลัง กุญแจดอกเดียวพยายามไขประตูสี่บานที่มีรูกุญแจต่างกัน เทียบกับประตูทุกบานที่เปลี่ยนเป็นแม่กุญแจแบบเดียวกัน
ก่อน: อัปเดตด้วยกุญแจ 8.4 ดอกเดียว ประตูอีกสามบานที่รูกุญแจต่างกันเปิดไม่ได้ หลัง: ไล่ดูทุกประตูก่อน แล้วเปลี่ยนให้เป็นแม่กุญแจชนิดเดียวกันหมด

สรุปคือ

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

ข้อควรจำที่ผมเก็บไว้ใช้ต่อคือ

  • ไฟล์ platform_check.php ถูกเขียนใหม่ทุกครั้งที่ update และเกณฑ์มาจากแพ็กเกจที่ต้องการ PHP สูงสุดใน lock file ซึ่งขึ้นกับว่าเรารัน update ด้วย PHP รุ่นไหน
  • ทุกตัวที่เปิด vendor ก้อนเดียวกัน ทั้ง FPM ของโดเมนหลัก FPM ของ subdomain cron และ systemd ต้องผ่านเกณฑ์เดียวกัน
  • ก่อน update ไล่หา runner ให้ครบ หลัง update เช็กให้ครบทุกทาง ไม่ใช่เฉพาะเว็บหลัก
  • ชี้ cron และ ExecStart ไปที่ binary เต็ม path ของรุ่นที่ตั้งใจ ไม่ใช้ symlink กลางที่คนอื่นก็ใช้ร่วมอยู่
  • ถ้า supervisor ขึ้น process ใหม่ให้ทันที อย่าคิดว่าเงียบแปลว่าปกติ ต้องมีตัวนับการรีสตาร์ตหรือสัญญาณเตือนซ้ำ

ผมยังไม่ได้ทำส่วนที่ให้สคริปต์แพตช์ไล่หา runner และเช็กทุก URL อัตโนมัติเอง ตอนนี้ยังเป็นการรัน list_runners.sh ด้วยมือก่อนทุกรอบ ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

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

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

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

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

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

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

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

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