สร้างเว็บฝากรูปสุดประหยัดของคุณเองบน Alibaba Cloud FC + OSS

บอกก่อนสามเรื่อง:
- เว็บฝากรูปตัวนี้ฟีเจอร์เรียบง่าย ตั้งค่าง่าย และปลอดภัยพอสมควร ถ้ามันไม่พอสำหรับคุณ ก็ส่งซอร์สโค้ดให้ AI ไปเลย — จะเพิ่มหรือลดฟีเจอร์อะไรก็เรื่องง่าย
AccessKey, รหัสผ่าน, โดเมน และชื่อ bucket ทุกตัวในบทความนี้เป็น ตัวอย่าง placeholder ทั้งหมด — เปลี่ยนเป็นของคุณเองก่อนจะก๊อปอะไรไปใช้- โค้ดเป็นแอป Node.js ไฟล์เดียว (ประมาณ 380 บรรทัด) โดย inject dependency ผ่าน Layer ของ FC ไม่มี hardcode ในโค้ดธุรกิจเลย และอ่านค่าคอนฟิกทั้งหมดจาก environment variable
ซอร์สโค้ด: https://github.com/ezzty/oss-upload
1. ทำไมต้องสร้างเว็บฝากรูปเอง
บล็อกสแตติกทุกอันโดยพื้นฐานแล้วต้องมีเว็บฝากรูป หลายคนประหยัดด้วยการใช้เว็บฝากรูปฟรีตลอดชีพของคนอื่น — แต่คนใจดีคนไหนล่ะที่ไม่เคยปิดตัว? แถมข้อมูลอยู่ในมือคนอื่น เราไม่มีอำนาจควบคุมอะไรเลย ก็ต้องคอยระแวงตลอดเวลา
หลังจากเจอหลุมมาเยอะพอแล้ว ผมเลยตัดสินใจสร้างเว็บฝากรูปของตัวเองในราคาถูกสุด ๆ เป้าหมายหลักคือ: ประหยัดเงิน ไม่จำกัดความจุ และปลอดภัย!
เว็บฝากรูปที่โฮสต์เองส่วนใหญ่ต้องมีเซิร์ฟเวอร์ของตัวเอง — เดือนละหลายสิบบาท แถมต้องแพตช์เอง — หรือไม่ก็รันได้แค่ในเครื่อง เลยอัปโหลดจากคอมพิวเตอร์เครื่องอื่นไม่ได้ สุดท้ายผมเลยตัดสินใจสร้างเว็บฝากรูปบนคลาวด์แบบเซิร์ฟเวอร์เลส: อัปโหลดได้ทุกที่ทุกเวลา
Alibaba Cloud Function Compute FC + OSS + CDN
FC คิดตามใช้จริง (แทบเป็น ¥0 ตอนทราฟฟิกน้อย) + ไม่มีฐานข้อมูล + ไม่ต้องดูแลระบบเลย แพ็กเกจ deploy เล็กแค่ 6KB อัปโหลดไม่บ่อยแทบไม่มีค่าใช้จ่าย
ที่เก็บรูปบน OSS ก็คิดตามใช้จริง — รูปสักหลายสิบคุญาต้องจ่ายแค่ไม่กี่หยวนต่อเดือน
เอา CDN มาวางข้างหน้าก็โหลดรูปได้ไวจนตาพร่า!
เว็บฝากรูปส่วนตัวไม่ใช่เป้าที่แฮกเกอร์อยากโจมตีเท่าไหร่ แต่ผมก็ยังเสริมความปลอดภัยไว้หลายชั้น: บังคับ PoW กัน brute force, ตรวจ magic number กันไฟล์ปลอม, เช็กขนาดสามชั้นกันยิงเพย์โหลดถล่ม, จำกัดโควตารายวันกันการใช้งานผิดวัตถุประสงค์ และ token อายุ 2 ชั่วโมงกันการขโมย
แต่ก็ใช่ว่าจะไม่มีข้อเสีย: ไม่รองรับหลายผู้ใช้ ไม่มีระบบจัดการอัลบั้ม ไม่มีสถิติการใช้งาน — จะลบรูปสักรูปยังต้องเข้า OSS console เองเลย
แต่มันเล็ก มันถูก และเหลือเฟือสำหรับใช้ส่วนตัว
2. สถาปัตยกรรมและลำดับการทำงานของ request
Browser upload.html
│ ① POST /login {user, pass}
│ ← 200 {token} (valid 24h)
│ ② POST /upload Authorization: Bearer *** + image binary
▼
Alibaba Cloud Function Compute FC (Node.js 18, HTTP trigger)
│ auth / rate limit / magic-number check / size check
│ ③ ossClient.put(key, stream)
▼
OSS Bucket (private read/write, public read disabled)
│ ④ CDN origin fetch (internal network, free)
▼
https://img.example.com/m/26j13/ab12cd.jpg ← public access domain
จุดตัดสินใจสำคัญสามข้อ:
- ตั้ง bucket เป็น private: ไม่มีใครเข้าถึงรูปด้วย direct link ได้ รูปจะเข้าถึงได้ผ่าน URL ที่ผูกกับโดเมน CDN เท่านั้น พอเปิด public read ปุ๊บ bucket ของคุณก็กลายเป็นทรัพยากรสาธารณะทันที — คนอื่นสามารถไล่ดูได้และดูดทราฟฟิกคุณไปใช้
- ต้องล็อกอินก่อนอัปโหลด: ไม่งั้นคนจะเอาโดเมน API ของคุณไปใช้เป็นเว็บฝากรูปฟรี แล้วบิลกับพื้นที่เก็บข้อมูลก็จะพุ่งแบบคุมไม่อยู่
- โค้ดธุรกิจไม่แตะ secret เลย: AK/SK อยู่ใน environment variable เท่านั้น — ไม่โผล่ในโค้ดหรือใน repo
3. เตรียมตัว
3.1 เปิดใช้บริการ
เปิดใช้ Function Compute FC และ Object Storage OSS (ทั้งคู่คิดตามใช้จริง ไม่ต้องซื้อล่วงหน้า)
3.2 สร้าง bucket
- ชื่อ: ต้องไม่ซ้ำกับใครในโลก เช่น
my-img - Region: เลือกที่ใกล้คุณ/ผู้ใช้ เช่น
East China 1 (Hangzhou)→oss-cn-hangzhou - การควบคุมการเข้าถึง: Private (สำคัญมาก)
- ประเภทที่เก็บข้อมูล: Standard — รูปที่เข้าถึงบ่อยควรใช้แบบนี้ อย่าเลือก Infrequent Access หรือ Archive เพราะมีระยะเวลาจัดเก็บขั้นต่ำ ถ้าลบก่อนกำหนดจะเสียเงินเพิ่มด้วย
3.3 สร้าง RAM user ที่จำกัดสิทธิ์แค่ bucket เดียว (แนะนำอย่างยิ่ง)
บทสอนหลายที่บอกให้ใช้ AccessKey ของ root account — อย่าทำเด็ดขาด ถ้า root AK รั่ว เท่ากับบัญชี Alibaba Cloud ทั้งบัญชีของคุณถูกยึด
RAM console → Policies → Create Policy → แท็บ JSON:
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:GetObject",
"oss:PutObject",
"oss:DeleteObject",
"oss:ListObjects",
"oss:HeadObject"
],
"Resource": [
"acs:oss:*:*:my-img",
"acs:oss:*:*:my-img/*"
]
}
]
}
ตั้งชื่อ policy ว่า ImageHostSingleBucket จากนั้น:
- RAM → Users → Create User ติ๊ก “Use permanent AccessKey for API call”
- ผูก เฉพาะ policy ข้างบนให้ user นี้
- สร้าง AK/SK แล้ว เซฟลง password manager ในเครื่องทันที
จุดตัดสินใจที่ตั้งใจไว้หลายข้อ:
- ไม่ให้
oss:ListBuckets— ถ้ารั่ว แฮกเกอร์ยังมองไม่เห็นด้วยซ้ำว่าคุณมี bucket อะไรบ้าง - ไม่ให้
oss:CopyObject— เว็บฝากรูปไม่ต้องใช้สิทธิ์คัดลอก ให้เท่าที่จำเป็นที่สุด - อย่าผูก
AliyunOSSFullAccess— นั่นจะทำให้ AK ของเว็บฝากรูปอ่าน data bucket ทุกอันที่คุณมีได้
4. เตรียม Layer ของ dependency
โค้ดธุรกิจพึ่งพาแค่ ali-oss ตัวเดียว เอามันไปไว้ใน Layer แล้วแพ็กเกจโค้ดก็จะเหลือแค่ไม่กี่ KB
ทางเลือก A: ดาวน์โหลด asset จาก release ตรง ๆ (แนะนำ)
โปรเจกต์มี Release เผยแพร่ไว้แล้ว — ทั้ง layer.zip และแพ็กเกจโค้ดดาวน์โหลดได้ตรง ๆ:
https://github.com/ezzty/oss-upload/releases/latest
| Asset | ขนาด | เนื้อหา |
|---|---|---|
layer.zip |
3.25 MB | ali-oss@6.23.0 และ dependency ของมัน — รวมทั้งหมด 2338 ไฟล์ |
oss-upload-code.zip |
~6 KB | index.js + package.json แพ็กเกจ deploy ของฟังก์ชัน |
ข้อดี: ไม่ต้องติดตั้ง Node.js ไม่ต้อง zip เอง และไม่มีปัญหาว่า “ไม่ตรงกับที่คนเขียนเทสต์ไว้” เพราะ npm install ในเครื่องคุณดึง SDK เวอร์ชันใหม่กว่า ใน layer มี แค่ซอร์สโค้ดของ npm package ไม่มี secret
ทางเลือก B: สร้างเอง
เมื่ออยากใช้ dependency เวอร์ชันอื่น หรือไม่ไว้ใจแพ็กเกจที่คนอื่นทำไว้:
mkdir -p layer-build/nodejs
cd layer-build
npm init -y
npm install ali-oss
# The directory MUST have this structure:
# nodejs/
# node_modules/
# ali-oss/
# ...its transitive dependencies
cd nodejs && zip -r ../layer.zip node_modules && cd ..
อัปโหลด
FC console → Layer Management → Create Layer:
- ชื่อ:
oss-upload-deps - Runtime: Node.js 18
- อัปโหลด
layer.zip
สร้าง layer ครั้งเดียวก็พอ ถ้าจะอัปเกรด
ali-ossทีหลัง ก็แพ็กเวอร์ชันใหม่ เผยแพร่ แล้วชี้ฟังก์ชันไปที่ layer เวอร์ชันใหม่ — ไม่ต้องแก้โค้ดธุรกิจเลย
5. Deploy ฟังก์ชัน
5.1 เอาโค้ดธุรกิจมา
แนะนำ: ใช้ asset จาก release ตรง ๆ — ดาวน์โหลด oss-upload-code.zip (~6 KB) จาก https://github.com/ezzty/oss-upload/releases/latest แตกไฟล์ก็ได้แพ็กเกจ deploy มาตรฐานเลย
ถ้าจะแพ็กเอง ใน root ของ zip ต้องมี แค่สองไฟล์นี้เท่านั้น (ห้ามมีโฟลเดอร์ห่อ ไม่งั้น Handler จะหา index.handler ไม่เจอ):
code.zip
├── index.js ← business source
└── package.json ← declares dependencies (for reference; actually loaded from the layer at runtime)
zip code.zip index.js package.json
5.2 สร้างฟังก์ชัน
- Runtime: Node.js 18
- Handler:
index.handler - Spec: 0.05 vCPU / 128 MB memory / 512 MB disk (สเปกเล็กสุดที่ใช้งานได้จริง)
- Layer configuration: ผูก layer
oss-upload-depsที่เพิ่งสร้าง
ระวังเรื่องหน่วยความจำ: โค้ดอ่าน body ทั้งก้อนเข้า memory (
bodyBuf) และการถอด base64 ก็สำเนาอีกชุด 128 MB พอสำหรับรูปทั่วไป แต่ถ้าคุณเพิ่มเพดานอัปโหลดเป็น 20 MB และอัปโหลดรูปใกล้เพดานบ่อย ๆ ก็เพิ่มหน่วยความจำตามไปด้วย
5.3 Environment variable (ต้องมี 8 ตัว — ขาดตัวใดตัวหนึ่งก็ไม่สตาร์ท)
environment variable ของฟังก์ชันมี 8 ตัวที่ต้องใส่ — ตั้งค่าตามในรูป
AUTH_USERadmin— ชื่อผู้ใช้ล็อกอินAUTH_PASS<ตั้งรหัสผ่านที่แข็งแรง>— รหัสผ่านล็อกอิน ใช้สตริงสุ่มยาว ๆJWT_SECRET<สตริงสุ่ม 32+ ตัวอักษร>— secret สำหรับเซ็น tokenOSS_ACCESS_KEY_IDLTAIxxxxxxxxxxxxxxxx— AK ของ RAM userOSS_ACCESS_KEY_SECRET<AccessKey Secret ของคุณ>— SK ของ RAM userOSS_BUCKETmy-img— ชื่อ bucketOSS_REGIONoss-cn-hangzhou— region ID ของ bucketCDN_DOMAINhttps://img.example.com— ต้องมี protocol scheme ด้วย ไม่งั้นจะสตาร์ทไม่ขึ้นทันที
ไม่บังคับ 1 ตัว:
ALLOWED_ORIGINSexample.com,myblog.net— origin ที่อนุญาตให้ cross-origin คั่นด้วยจุลภาค ถ้าไม่ใส่ฟังก์ชันก็ยังสตาร์ทได้; ค่านี้จะถูกใช้ เฉพาะตอนเปิด CORS ระดับโค้ด เท่านั้น (โดยดีฟอลต์แพลตฟอร์มแค่ echoOriginกลับไป เลยไม่เคยเรียกisValidOriginเลย) ถ้าเปิด CORS ระดับโค้ดจริง ๆ ต้องตั้งค่านี้ ไม่งั้นค่า fallbackexample.comจะบล็อกการอัปโหลดจากเบราว์เซอร์ทั้งหมด

5.4 ผูกโดเมนของตัวเอง
FC console → Domain Management → Add Domain แล้วทำตามขั้นตอนเพื่อตั้งค่า CNAME กับ certificate นั่นคือวิธีที่คุณจะได้ที่อยู่ API แบบ https://api.example.com
6. หน้า frontend
upload.html เป็น frontend ไฟล์เดียว — เอาไปวางในโฟลเดอร์เว็บของคุณได้เลย
ต้องแก้อันนึง — หา _FC_PARTS:
// Change this to your Function Compute custom domain
const _FC_PARTS = ['https://', 'api.example.com'];
เพดานขนาดไฟล์ต้องตรงกับฝั่ง backend (ทั้งคู่ดีฟอลต์ 20MB):
if (file.size > 20 * 1024 * 1024) { /* block */ }
แต่ละฝั่งมีเพดานของตัวเอง — อันที่เล็กกว่าชนะ แก้แค่ frontend ไฟล์ใหญ่จะโดน 413 จากฝั่งเซิร์ฟเวอร์; แก้แค่ backend ผู้ใช้จะโดนบล็อกในเบราว์เซอร์ก่อนที่ request จะออกไปด้วยซ้ำ
7. อยากลงลึกกว่านี้? รายละเอียดหลักอยู่ใน README ของซอร์ส
นี่คือขั้นตอนครบถ้วนในการทำให้เว็บฝากรูปที่โฮสต์เองรันได้ ทำไมโค้ดถึงเขียนแบบนี้ รายละเอียดการทำงานของแต่ละชั้นป้องกัน และเช็กลิสต์ตรวจรับก่อนเปิดใช้ อยู่ใน README ของ repo ซอร์สทั้งหมด — เปิดดูตอนต้องใช้ได้เลย:
https://github.com/ezzty/oss-upload → หัวข้อ “Core implementation (code level)” และ “Pre-launch acceptance” ใน README.md
บทความบล็อกนี้แค่อยากอธิบายว่า “ทำไมต้องโฮสต์เอง ทำยังไงให้รันได้ และต้องระวังอะไร” ตอน deploy จริง README คือเอกสารเทคนิคฉบับสมบูรณ์
8. ค่าใช้จ่าย
คิดตามใช้จริง แบ่งเป็นสามส่วน:
| รายการ | การคิดเงิน | ขนาด |
|---|---|---|
| FC compute | Compute CU (vCPU + memory × duration) + จำนวนครั้งที่เรียก | มีโควตาฟรี; เว็บฝากรูปส่วนตัวมักอยู่ใกล้ ๆ โควตานี้ |
| OSS storage | ¥/GB/เดือน | Standard storage ประมาณ ¥0.09–0.12/GB/เดือน |
| OSS requests | ต่อ request | น้อยมากจนไม่ต้องคิด |
| CDN traffic | ¥/GB | ดูด้านล่าง |
ทราฟฟิกคือค่าใช้จ่ายก้อนใหญ่ที่สุด และปรับได้ง่ายที่สุด:
- อัปโหลดเข้า OSS: ฟรี
- OSS internal network (FC ↔ OSS ใน region เดียวกัน): ฟรี
- OSS ขาออกทาง public internet: คิดเงิน (ประมาณ ¥0.5/GB ขึ้นกับช่วงเวลาและ region)
- ผ่าน CDN: CDN origin fetch ใช้ internal network ทราฟฟิกต้นทางเลยฟรี — จ่ายแค่ค่า CDN traffic
สรุป: เอา CDN มาวางหน้าเว็บฝากรูปเสมอ — เร็วขึ้นและเลี่ยงค่า egress ของ OSS ได้ด้วย และจำไว้ว่า FC กับ OSS ต้องอยู่ region เดียวกัน ถ้าคนละ region จะวิ่งผ่าน public internet ซึ่งช้าและอาจมีค่าใช้จ่ายทราฟฟิกเพิ่ม
จากการทดสอบของผมเอง อัปโหลดรูปสักหลายสิบรูปต่อเดือนจ่ายประมาณ ¥1 ต่อเดือน
9. การดูแลและอัปเดต
แก้โค้ดธุรกิจ (เช่น รูปแบบ URL ของรูป):
- แก้
index.jsในเครื่อง zip code.zip index.js package.json- Console → Code Deployment → Upload → Deploy
- รัน acceptance probe ใน README เพื่อยืนยันว่าเวอร์ชันใหม่ขึ้นจริง
แก้ frontend:
- แก้
upload.html - อัปโหลดขึ้นเว็บ; ลองตั้ง
Cache-Control: no-cacheไว้ด้วย ไม่งั้นผู้ใช้อาจได้เวอร์ชันเก่า
อัปเกรด dependency:
- รัน
npm install ali-ossใหม่แล้วแพ็กlayer.zip - เผยแพร่ layer เวอร์ชันใหม่ใน console
- สลับฟังก์ชันไปใช้ layer เวอร์ชันใหม่
หมุนเวียน key (ทำเป็นระยะ ๆ):
- สร้าง AK/SK ใหม่ใน RAM
- อัปเดต environment variable ของ FC → deploy
- พอยืนยันว่าบริการยังปกติแล้ว ปิดใช้งาน AK ตัวเก่าใน RAM console