Frappe Framework ที่ใช้สร้าง ERPNext คือ full-stack application ที่รวม web server, background jobs worker, scheduler, WebSocket Support และอีกหลายเซอร์วิสไว้ด้วยกัน
การที่เราเข้าใจว่าเซอร์วิสเหล่านี้ทำงานด้วยกันอย่างไร เป็นสิ่งสำคัญที่เราควรจะรู้ก่อนที่จะ Deploy ใน Production (ไม่ว่าจะเป็น bare-metal, vm หรือ container)
บทความนี้จะพูดถึง 4 ประเด็นหลักๆของระบบ:
- พื้นที่จัดเก็บข้อมูลถาวร - ข้อมูลใดบ้างที่เราควรจัดเก็บและ Backup
- ภาพรวมเซอร์วิส - การทำงานร่วมกันของระบบต่างๆ
- Domain Name Routing - เส้นทาง / ขั้นตอนการเดินทางของ HTTP request ไปสู่เว็บไซต์ของเรา
- โครงสร้าง Folder ของ Frappe - โครงสร้างโฟลเดอร์ภายใน
~/bench/sites
พื้นที่จัดเก็บข้อมูลถาวร
Frappe มีโฟลเดอร์ทั้งหมด 3 โฟลเดอร์ที่เราควรจะต้องดูแลและ Backup เป็นพิเศษ
| ป้ายชื่อ | พื้นที่จัดเก็บ | พาธเริ่มต้น | สิ่งที่เก็บ |
|---|---|---|---|
| A | Frappe Storage | ~/bench/sites | ไฟล์ไซต์, ไฟล์อัปโหลด, ไฟล์ส่วนตัว, และการตั้งค่าต่อไซต์ (site_config.json) |
| B | MariaDB Storage | /var/lib/mysql | ข้อมูลใน Database ทั้งหมด - DocTypes, ธุรกรรม, ข้อมูลผู้ใช้ ฯลฯ |
| C | Redis Queue Storage | /data | คิวของ Worker |
ทำไมสิ่งนี้จึงสำคัญในทางปฏิบัติ
เมื่อคุณรัน Frappe บน Docker โฟลเดอร์ทั้งสามนี้จะต้องใช้ named volume หรือ bind mount รองรับ หากลืมโฟลเดอร์ใดโฟลเดอร์หนึ่งจะทำให้เกิดการสูญหายของข้อมูล:
- การสูญหายของ A (
~/bench/sites) จะลบไฟล์ที่อัปโหลดและการตั้งค่าไซต์ - แอปอาจบูตได้แต่จะกำหนดค่าผิดพลาดทั้งหมด - การสูญหายของ B (
/var/lib/mysql) ข้อมูลใน Database จะหายทั้งหมด - เอกสารการเงิน การบัญชี และอื่นๆที่อยู่ใน Database - การสูญหายของ C (
/data) จะทำให้งาน background ที่อยู่ในคิวแต่ยังไม่ได้รันหายไป
Redis Cache ไม่รวมอยู่ในรายการนี้โดยตั้งใจ - ถ้า Cache หายระบบจะดึงข้อมูลมาเก็บใหม่โดยอัตโนมัติ อยู่แล้ว จึงไม่ต้องกังวลอะไร
ภาพรวมเซอร์วิส
การ deploy Frappe ใน production ประกอบด้วยกระบวนการที่ทำงานร่วมกันหลายอย่าง แผนภาพข้างบนแบ่งพวกมันออกเป็นสามหมวด
เว็บเซอร์วิส (Web Services)
Nginx (พอร์ต 80)
Nginx ที่ที่ทุก request จากบราวเซอร์จะมาถึงที่นี่ก่อน โดยอิงจาก URL path, Nginx จะตัดสินใจว่าจะไปทางไหน:
| Path | การกระทำ |
|---|---|
/assets/* | ส่งไฟล์ static โดยตรงจาก ~/bench/sites/assets/ |
/files/* | ส่งไฟล์ที่อัปโหลดแบบสาธารณะจาก ~/bench/sites/$host/public/files/ |
/protected/* | ส่งไฟล์ส่วนตัวจาก ~/bench/sites/$host/private/files/ (ผ่าน X-Accel-Redirect - Frappe web server อนุมัติ request ก่อน แล้ว Nginx จึงส่งไฟล์) |
/socket.io/* | Reverse-proxy ไปยัง WebSocket service |
/* (ทุกอย่างอื่น) | Reverse-proxy ไปยัง Gunicorn web server |
WebSocket / Socket.IO (พอร์ต 9000)
คำสั่งที่ใช้ (โดยง่าย): node frappe-bench/apps/frappe/socketio.js
จัดการฟีเจอร์ real-time: การแจ้งเตือนบนเดสก์ท็อป, การอัปเดต form แบบสด, ความคืบหน้าการพิมพ์, และ push event อื่นๆ ไคลเอนต์เปิดการเชื่อมต่อถาวรที่นี่ โดย Frappe web server จะส่ง event ไปยังกระบวนการนี้เมื่อมีการเปลี่ยนแปลง
Frappe Web Server / Gunicorn (พอร์ต 8000)
คำสั่งที่ใช้ (โดยง่าย): frappe-bench/env/bin/gunicorn frappe.app:application
แอปพลิเคชัน Python WSGI หลัก การ render หน้าเว็บ, API call, และการส่ง form ทุกครั้งจะจบลงที่นี่ Gunicorn จะ spawn worker process หลายตัวเพื่อรองรับ request พร้อมกัน
บริการ Background (Background Services)
คำสั่งที่ใช้ (โดยง่าย): bench worker / bench scheduler
Frappe ใช้ คิวงานที่ขับเคลื่อนด้วย Redis เพื่อรันงานแบบ asynchronous มี worker process 4 ตัว:
| Worker | วัตถุประสงค์ |
|---|---|
| Short | งานเร็วและเบา (ส่งอีเมลเดียว, อัปเดต cache entry) |
| Default | งาน background มาตรฐาน |
| Long | งานหนักหรืองานที่ใช้เวลานาน (bulk imports, การสร้างรายงาน, scheduled reports) |
| Scheduler | อ่าน scheduled tasks แบบ cron ที่กำหนดไว้ในแต่ละแอปและส่งงานเข้าคิวที่เหมาะสม |
Scheduler ไม่รันงานโดยตรง - มันเพียงแค่ enqueue งาน Workers Short/Default/Long จะดึงงานจากคิวและรัน
บริการสนับสนุน (Support Services)
คำสั่งที่ใช้ (โดยง่าย):
| บริการ | พอร์ต | บทบาท |
|---|---|---|
| MariaDB | 3306 | ฐานข้อมูลเชิงสัมพันธ์ - แหล่งความจริงสำหรับข้อมูลแอปพลิเคชันทั้งหมด |
| Redis Queue | 6379 | คิวงาน - workers อ่านจากที่นี่ |
| Redis Cache | 6379 | Cache ระดับแอปพลิเคชัน - เพิ่มความเร็วในการโหลดหน้าเว็บและ query ซ้ำ |
หมายเหตุ: Redis Queue และ Redis Cache มักรันเป็น Redis instance แยกกัน (พอร์ตต่างกันหรือ container service ต่างกัน) แม้ว่าทั้งคู่จะใช้พอร์ต 6379 โดยปกติ ตรวจสอบ
common_site_config.jsonของคุณเพื่อดูที่อยู่จริง
Bench CLI
bench คือเครื่องมือ command-line ที่ใช้จัดการการติดตั้ง Frappe ทั้งหมด - การสร้างไซต์, การติดตั้งแอป, การรัน migrations, และการเริ่ม/หยุดบริการ มันไม่ใช่ daemon ที่รันตลอดเวลา แต่ใช้โดย administrators แบบ interactive
ชื่อโดเมนไซต์: ส่วนประกอบสำคัญสำหรับ Production
หนึ่งในการตัดสินใจออกแบบที่โดดเด่นที่สุดของ Frappe คือ ชื่อโดเมนคือตัวระบุไซต์ Frappe bench เดียวสามารถโฮสต์หลายไซต์ได้ และชื่อโดเมนของ request ขาเข้าคือสิ่งที่กำหนดว่าจะใช้ไซต์ใด (และฐานข้อมูลใด)
วิธีการทำงาน
-
Nginx ถูกกำหนดค่าด้วย
server_nameที่ตรงกับโดเมนของไซต์:server { listen 80; server_name your-domain.com; root /home/frappe/frappe-bench/sites; ... } -
Nginx ฉีดชื่อโดเมนเข้า HTTP header ที่กำหนดเอง ก่อนส่งต่อ request:
# Proxy pass ไปยัง bench-socketio location /socket.io/ { proxy_set_header X-Frappe-Site-Name $host; ... proxy_pass http://bench-socketio; } # Proxy pass ไปยัง bench-web location / { proxy_set_header X-Frappe-Site-Name $host; ... proxy_pass http://bench-web; } -
แอปพลิเคชัน Frappe อ่าน
X-Frappe-Site-Nameและค้นหาโฟลเดอร์ที่ตรงกันภายใต้~/bench/sites/หากโฟลเดอร์ชื่อyour-domain.comมีอยู่ Frappe จะเปิดyour-domain.com/site_config.jsonเพื่อรับข้อมูล credentials ของฐานข้อมูล:{ "db_name": "_16d15c20aae1ed29", "db_password": "a78rFm5ToTLHxQfA", "db_type": "mariadb", "db_user": "_16d15c20aae1ed29", "encryption_key": "...=" }
ผลกระทบในทางปฏิบัติ
- ชื่อโฟลเดอร์ต้องตรงกับชื่อโดเมน ที่ส่งถึง Nginx หากไม่ตรงกัน (เช่น
www.your-domain.comกับyour-domain.com) Frappe จะรายงานว่าไม่มีไซต์นั้น - เนื่องจากโดเมนมีความสำคัญเชิงโครงสร้าง (มันคือชื่อไดเรกทอรี) การเปลี่ยนชื่อไซต์ จึงต้องย้ายโฟลเดอร์และอัปเดต DNS และ Nginx config
โครงสร้างโฟลเดอร์ไซต์ Frappe
ทุกอย่างภายใต้ ~/bench/sites แบ่งออกเป็นสองหมวดกว้างๆ: ไฟล์แอปพลิเคชัน (artifacts ที่สร้างขึ้นส่วนใหญ่เมื่อ build/install) และ พื้นที่จัดเก็บไซต์ (ข้อมูลถาวรจริงที่ไม่ซ้ำกันสำหรับแต่ละไซต์)
~/bench/sites/
├── apps.json # Metadata เกี่ยวกับแอปที่ติดตั้ง
├── apps.txt # รายการแอปที่ติดตั้งตามลำดับ
├── assets/ # CSS, JS ที่ build แล้ว และ frontend artifacts อื่นๆ
├── common_site_config.json # การตั้งค่า global ที่ใช้ร่วมกันโดยทุกไซต์
├── your-domain.com/ # หนึ่งโฟลเดอร์ต่อหนึ่งไซต์
│ ├── site_config.json
│ ├── public/
│ │ └── files/ # ไฟล์ที่อัปโหลดซึ่งเข้าถึงได้สาธารณะ
│ └── private/
│ └── files/ # ไฟล์ที่อัปโหลดแบบส่วนตัว (ต้องมีการยืนยันตัวตนในการดาวน์โหลด)
└── another-domain.com/ # อีกไซต์หนึ่งไฟล์แอปพลิเคชัน (apps.json, apps.txt, assets/)
สิ่งเหล่านี้คือ artifacts จาก bench build และ bench install-app ไม่ใช่ข้อมูลผู้ใช้ แต่เป็นสิ่งที่ได้จาก source code ของแอป ในการตั้งค่าที่ใช้ Docker ไฟล์เหล่านี้ควร build เข้าไปใน image หรือ copy/link จาก image เมื่อ container เริ่มต้น - ไม่จำเป็นต้องอยู่ใน persistent volume เพราะสามารถสร้างซ้ำจาก source ได้
การตั้งค่า Global (common_site_config.json)
ไฟล์นี้เก็บการตั้งค่าที่ใช้กับทุกไซต์บน bench - ที่อยู่ Redis, ขีดจำกัดขนาดไฟล์, developer mode flags ฯลฯ คุณสามารถจัดการได้ด้วย:
bench set-config -g redis_queue redis://redis-queue:6379แฟล็ก -g จะเขียนไปยัง common_site_config.json แทนที่จะเป็น config ของไซต์ใดไซต์หนึ่ง
คุณสามารถถือว่าไฟล์นี้เป็น persistent storage (mount เป็น volume) หรือ สร้างใหม่แบบ dynamic เมื่อเริ่มต้นโดยใช้ bench set-config วิธีหลังนี้เป็นเรื่องปกติในสภาพแวดล้อม container ที่ configuration ถูกฉีดผ่าน environment variables
พื้นที่จัดเก็บไซต์ (your-domain.com/)
นี่คือ พื้นที่จัดเก็บข้อมูลถาวรจริง สำหรับแต่ละไซต์ ประกอบด้วย:
site_config.json- credentials ของฐานข้อมูลและการตั้งค่าเฉพาะไซต์public/files/- ไฟล์ที่ผู้ใช้อัปโหลดซึ่งเข้าถึงได้สาธารณะ (เช่น รูปภาพสินค้า)private/files/- ไฟล์ที่ผู้ใช้อัปโหลดซึ่งต้องมีการยืนยันตัวตนในการเข้าถึง (เช่น สลิปเงินเดือน, เอกสารลับ)
โฟลเดอร์นี้ต้องอยู่ใน persistent volume หากสูญหาย ไฟล์ที่อัปโหลดจะหายไปและไซต์จะสูญเสียข้อมูล database connection
สรุป
| ประเด็น | สิ่งสำคัญ |
|---|---|
| Persistent Storage | สาม volumes: ~/bench/sites, /var/lib/mysql, Redis /data |
| Services | Nginx → Gunicorn + Socket.IO, รองรับโดย Workers + Scheduler + MariaDB + สอง Redis instances |
| Domain Routing | ชื่อโดเมน = ชื่อโฟลเดอร์ไซต์; ส่งผ่าน header X-Frappe-Site-Name |
| Folder Layout | assets/ คือ build artifact; your-domain.com/ คือข้อมูลเดียวที่คุณต้องคงไว้ |
การเข้าใจสถาปัตยกรรมนี้ทำให้ง่ายต่อการออกแบบการ deploy ที่แข็งแกร่ง - ไม่ว่าจะเขียน docker-compose.yml, Helm chart, หรือ Ansible playbook แบบ bare-metal สิ่งไม่แปรผันหลักยังคงเหมือนเดิมเสมอ: รักษา three persistent volumes ให้ปลอดภัย, ทำให้ชื่อโดเมนสอดคล้องกัน, และให้บริการต่างๆ สื่อสารผ่านพอร์ตที่กำหนด