ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

ตั้ง Nginx เป็น Reverse Proxy พร้อม Cache ลดภาระ Backend
Home ตั้ง Nginx เป็น Reverse Proxy พร้อม Cache ลดภาระ Backend

ตั้ง Nginx เป็น Reverse Proxy พร้อม Cache ลดภาระ Backend

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

คู่มือนี้ตั้ง Nginx เป็น Reverse Proxy หน้า Backend ไม่ว่าจะเป็น PHP-FPM, Node.js หรือ Container แล้วเปิดแคชอย่างปลอดภัย พร้อมกฎที่ต้องระวังเพื่อไม่ให้ผู้ใช้เห็นข้อมูลของคนอื่น

สิ่งที่ต้องเตรียม

  • Nginx บน Linux และสิทธิ์ sudo
  • Backend ที่ทำงานอยู่แล้ว เช่น แอปที่ฟังพอร์ต 3000 บน localhost
  • พื้นที่ดิสก์สำหรับเก็บแคช เริ่มที่ 1-2 GB ก็เพียงพอสำหรับเว็บทั่วไป

ขั้นตอนที่ 1: ตั้ง Reverse Proxy พื้นฐานก่อน

sudo nano /etc/nginx/sites-available/example.com
upstream app_backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}
sudo nginx -t && sudo systemctl reload nginx

ส่วน keepalive 32 คู่กับ proxy_set_header Connection "" ทำให้ Nginx ใช้การเชื่อมต่อเดิมซ้ำกับ Backend แทนที่จะเปิดใหม่ทุกคำขอ ซึ่งช่วยลดภาระได้มากในเว็บที่มีทราฟฟิกสูง

ขั้นตอนที่ 2: ประกาศพื้นที่เก็บแคช

ประกาศในบล็อก http ไม่ใช่ใน server

sudo nano /etc/nginx/nginx.conf
proxy_cache_path /var/cache/nginx/app
                 levels=1:2
                 keys_zone=app_cache:20m
                 max_size=2g
                 inactive=60m
                 use_temp_path=off;
sudo mkdir -p /var/cache/nginx/app
sudo chown www-data:www-data /var/cache/nginx/app

ความหมายของค่าแต่ละตัว keys_zone=app_cache:20m คือพื้นที่หน่วยความจำสำหรับเก็บกุญแจ ซึ่ง 20 MB เก็บได้ราวหนึ่งแสนหกหมื่นรายการ max_size=2g คือเพดานพื้นที่บนดิสก์ และ inactive=60m คือรายการที่ไม่ถูกเรียกเลยใน 60 นาทีจะถูกลบทิ้ง

ขั้นตอนที่ 3: เปิดแคชอย่างปลอดภัย

map $http_cookie $skip_cache {
    default 0;
    # ผู้ใช้ที่ล็อกอินอยู่ต้องไม่ถูกแคช
    ~*session_id 1;
    ~*wordpress_logged_in 1;
    ~*laravel_session 1;
}

map $request_method $skip_cache_method {
    default 1;
    GET  0;
    HEAD 0;
}

server {
    # ... ส่วนเดิม ...

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_cache app_cache;
        proxy_cache_key "$scheme$request_method$host$request_uri";
        proxy_cache_valid 200 301 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_bypass $skip_cache $skip_cache_method $arg_nocache;
        proxy_no_cache     $skip_cache $skip_cache_method;

        # ใช้แคชเดิมตอบต่อได้หาก Backend ล่มหรือช้า
        proxy_cache_use_stale error timeout updating
                              http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;

        add_header X-Cache-Status $upstream_cache_status always;
    }
}
sudo nginx -t && sudo systemctl reload nginx

ขั้นตอนที่ 4: เข้าใจสามบรรทัดที่สำคัญที่สุด

  • proxy_cache_use_stale ทำให้เว็บยังเปิดได้แม้ Backend ล่ม โดยตอบจากแคชเดิมที่หมดอายุแล้ว ผู้ใช้เห็นข้อมูลเก่าเล็กน้อยดีกว่าเห็นหน้า 502
  • proxy_cache_lock ป้องกันปัญหาที่เมื่อแคชหมดอายุพร้อมกัน คำขอหลายร้อยรายการวิ่งไป Backend พร้อมกันจนล่ม เมื่อเปิดไว้จะมีคำขอเดียวที่ไปดึงของใหม่ ที่เหลือรอผลนั้น
  • X-Cache-Status คือ header ที่ให้คุณตรวจสอบได้ว่าคำขอนั้นถูกตอบจากแคชหรือไม่ ควรมีไว้เสมอตอนตั้งค่า

ขั้นตอนที่ 5: ทดสอบว่าแคชทำงาน

curl -sI https://example.com/ | grep -i x-cache-status
curl -sI https://example.com/ | grep -i x-cache-status

ครั้งแรกควรได้ MISS ครั้งที่สองควรได้ HIT ค่าอื่นที่พบได้คือ BYPASS เมื่อตรงเงื่อนไขข้าม EXPIRED เมื่อหมดอายุและกำลังดึงใหม่ และ STALE เมื่อตอบด้วยของเก่าเพราะ Backend มีปัญหา

ทดสอบว่าผู้ใช้ที่ล็อกอินไม่โดนแคช

curl -sI -H 'Cookie: wordpress_logged_in_abc=1' https://example.com/ | grep -i x-cache-status

ควรได้ BYPASS หากได้ HIT แปลว่ากฎยังไม่ถูกต้องและมีความเสี่ยงที่ผู้ใช้จะเห็นหน้าของคนอื่น ต้องแก้ทันที

ขั้นตอนที่ 6: เพิ่มกฎสำหรับไฟล์คงที่

location ~* \.(?:css|js|woff2|png|jpe?g|gif|svg|webp|ico)$ {
    proxy_pass http://app_backend;
    proxy_cache app_cache;
    proxy_cache_valid 200 30d;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    add_header X-Cache-Status $upstream_cache_status always;
    add_header Cache-Control "public, max-age=2592000, immutable";
}

ไฟล์ที่มีชื่อพร้อมรหัสรุ่น เช่น app.4f3a2b.js แคชได้ยาวเพราะเปลี่ยนเนื้อหาเมื่อไรชื่อไฟล์ก็เปลี่ยนตาม

ขั้นตอนที่ 7: ล้างแคช

Nginx รุ่นโอเพนซอร์สไม่มีคำสั่งล้างแคชรายหน้าในตัว วิธีที่ใช้ได้จริงมีสองทาง

# ล้างทั้งหมด
sudo rm -rf /var/cache/nginx/app/*
sudo systemctl reload nginx

# ล้างเฉพาะหน้าเดียว โดยคำนวณกุญแจแล้วหาไฟล์
KEY="httpsGETexample.com/about/"
HASH=$(printf '%s' "$KEY" | md5sum | cut -d' ' -f1)
sudo find /var/cache/nginx/app -name "$HASH" -delete

อีกทางคือใช้ตัวแปร $arg_nocache ที่ตั้งไว้ในขั้นตอนที่ 3 เรียกหน้าด้วย ?nocache=1 เพื่อบังคับดึงของใหม่มาเก็บทับ

ขั้นตอนที่ 8: เฝ้าดูว่าแคชได้ผลแค่ไหน

sudo nano /etc/nginx/nginx.conf
log_format cached '$remote_addr - $upstream_cache_status '
                  '"$request" $status $body_bytes_sent '
                  '$request_time';
access_log /var/log/nginx/access.log cached;
sudo awk '{print $4}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
sudo du -sh /var/cache/nginx/app

อัตรา HIT ที่ดีสำหรับเว็บเนื้อหาทั่วไปคือ 70% ขึ้นไป หากต่ำกว่านั้นมาก ให้ตรวจว่ากฎข้ามแคชกว้างเกินไปหรือไม่ และ proxy_cache_valid สั้นเกินไปหรือเปล่า

ปัญหาที่พบบ่อย

ได้ MISS ทุกครั้ง

สาเหตุที่พบบ่อยคือ Backend ส่ง header Cache-Control: no-cache หรือ Set-Cookie กลับมาทุกคำขอ ซึ่ง Nginx จะไม่แคชให้โดยอัตโนมัติ ตรวจด้วย curl -sI http://127.0.0.1:3000/ หากจำเป็นให้สั่งเพิกเฉยด้วย proxy_ignore_headers Cache-Control Expires Set-Cookie; แต่ต้องมั่นใจว่าหน้านั้นไม่มีข้อมูลส่วนบุคคล

ผู้ใช้เห็นข้อมูลของคนอื่น

เป็นปัญหาร้ายแรงที่สุดของการแคช เกิดจากหน้าที่มีข้อมูลเฉพาะบุคคลถูกแคชไว้ ตรวจกฎ proxy_cache_bypass ให้ครอบคลุมคุกกี้ของระบบล็อกอินทั้งหมด และเพิ่มคุกกี้ผู้ใช้เข้าไปใน proxy_cache_key หากจำเป็น

แก้เนื้อหาแล้วหน้าเว็บยังเป็นของเดิม

ปกติของการแคช รอจนหมดอายุตาม proxy_cache_valid หรือล้างแคชตามขั้นตอนที่ 7 หากต้องล้างบ่อยให้ตั้งระบบล้างอัตโนมัติเมื่อมีการบันทึกเนื้อหา

ดิสก์เต็มเพราะแคช

ตรวจว่า max_size ถูกตั้งไว้แล้ว และค่านั้นน้อยกว่าพื้นที่ว่างจริง Nginx จะลบรายการที่เก่าที่สุดเองเมื่อถึงเพดาน

ต้องการให้ทีมวิศวกรช่วยปรับโครงสร้างให้รองรับทราฟฟิกสูง ติดต่อ THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security