การใช้ Express รันหลายโดเมนในเครื่องเดียวด้วย Nginx

Express เป็นเฟรมเวิคยอดนิยมสำหรับพัฒนาสมัยที่มุ่งเข้าสู่การพัฒนาเว็บในรูปแบบใหม่ ๆ ไลบารี่เจ๋ง ๆ ที่อยู่ใน NodeJS มีมากจนหลายคนย้ายจาก PHP ASP.net มาสู่ NodeJS เต็มตัว จากเดิมที่ PHP ถูกจำกัดทรัพยากรต่าง ๆ แล้วส่งผลให้ปัญหามีมาก เช่น ไม่สามารถอัพโหลดไฟล์ขนาดใหญ่ได้ เพราะโฮสติ้งจำกัดไว้ แต่เมื่อย้ายมาใช้ NodeJS นักพัฒนาสามารถควบคุมทรัพยากรได้เองทั้งหมด ทำให้เกิดปัญหาจุกจิกกับโปรเจคเล็ก ๆ น้อยกว่าด้วย

แต่ปัญหาของการใช้ NodeJS ก็มีเช่นกัน เดิมการใช้ภาษา PHP มักจะใช้คู่กับระบบจัดการโฮสติ้งอย่าง DirectAdmin หรือ cPanel ซึ่งกรณีที่เราจะใช้หลายโดเมน ระบบจัดการเหล่านี้จะทำหน้าที่ไปแก้คอนฟิกต่าง ๆ ให้ทั้งหมด ทำให้นักพัฒนาแค่นั่งคลิกเม้าส์ก็เปิดเว็บใหม่คนละโดเมนได้เลย แต่ NodeJS ไม่เป็นแบบนั้น ตัว NodeJS ไม่มีระบบจัดการมาให้ ทำให้เมื่อต้องการพัฒนางานใหญ่ขึ้น มีโดเมนหลายโดเมนมากขึ้น การรวมการจัดการทั้งหมดไว้ไฟล์เดียว แล้วรันอยู่โปรเซตเดียวไม่ใช่เรื่องดีแน่ ๆ ดังนั้น การแยกแต่ละโดเมนออกจากแต่ละชุดโปรเจค จะทำให้การพัฒนาง่ายกว่า และไม่เสื่ยงที่โปรเจคจะพัง ซึ่งเราจะใช้ Nginx นี่แหละมาเป็นตัวช่วยแยกแต่ละโปรเจคออกจากกัน

Nginx ผมรู้จัดครั้งแรกในฐานะพร๊อกซี่เซิร์ฟเวอร์ที่คอนฟิกได้ค่อนข้างง่าย และรองรับโหลดจำนวนมาก ๆ ได้ ผู้ให้บริการโฮสติ้งมักใช้ Nginx มารับหน้าแทนที่จะให้ Apache มารับตรง ๆ เพราะจะแบ่งเบาภาระมากกว่า (แต่เชื่อเถอะ ถ้ามีทรัพยากรเหลือ ๆ เอา Apache มารับตรง ๆ ปัญหาน้อยกว่ามี Nginx มากั้นเยอะ) ภาระส่วนของไฟล์ที่ไม่มีการเปลี่ยนแปลงบ่อย ๆ เช่น ไฟล์ภาพ ไฟล์ .css .js ตัว Nginx ก็จะทำหน้าที่แครชไฟล์เหล่านี้ให้ ทำให้ Apache ทำงานเฉพาะไฟล์ที่ต้องประมวลผล หรือมีการเปลี่ยนแปลงตลอดเวลา เช่น .php ที่ผู้ใช้แต่ละคนจะได้รับข้อมูลในหน้าเว็บไม่เหมือนกัน

Nginx ในบทความนี้ก็จะถูกเอามาใช้เป็นพร๊อกซี่เช่นเดียวกัน แต่ไม่ใช่ในฐานะตัวช่วยแครชไฟล์ แต่ในฐานะตัวช่วยกระจายแต่ละโดเมนไปในแต่ละพอร์ตภายใน

หลักการคือ เราจะแยกเว็บโปรเจค 1 เว็บ ต่อ 1 พอร์ตภายใน เช่น มีเว็บ A และเว็บ B ก็จะให้เว็บ A รันอยู่บนพอร์ต 3000 และเว็บ B รันอยู่บนพอร์ต 3001 เป็นต้น (และพอร์ตเหล่านี้ไม่ควรปล่อยให้เข้าถึงจากสาธาณะ เพราะถ้าเจอการโจมตีอะไรมาจะได้ไม่ต้องย้ายพอร์ตหนี ไปจัดการที่ Nginx เป็นพอ) จากนั้นใช้ Nginx ที่รันอยู่ที่พอร์ต 80 เป็นพร๊อกซี่ดึงข้อมูลตามแต่ละพอร์ตมาแสดง โดยกำหนดไว้ชัดเจนว่าถ้าเจอ host เป็นของเว็บ A ก็จะเอาข้อมูลมาจากพอร์ต 3000 และถ้าเจอ host ของเว็บ B จะไปเอาจากพอร์ต 3001 มา เท่านี้แต่ละโปรเจคก็จะแยกไฟล์กันได้ และใช้หลายโดเมนในเซิร์ฟเวอร์เดียวกันได้

มาเริ่มกันดีกว่า !

การติดตั้งโปรแกรมต่าง ๆ จะแตกต่างไปแต่ละ OS แต่มีขั้นตอนการทำงานที่คล้ายกัน ผมใช้ CentOS 7 มีขั้นตอนการติดตั้งโปรแกรมต่าง ๆ ดังนี้

ติดตั้ง Nginx

อัพเดทตัว yum ให้รู้จักแพคเกจมากขึ้น (กรณีไม่ได้รันด้วยสิทธิ์ root ต้องเติม sudo ข้างหน้าด้วย)

yum install epel-release

ติดตั้ง Nginx

yum install nginx

สั่ง start Nginx ขึ้นมา

systemctl start nginx

หากยังไม่เคยเปิดพอร์ต 80 กับ 443 ต้องเปิดก่อน โดยส่งคำสั่งต่อไปนี้ไปบอกไฟล์วอล

firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload

แล้วลองเข้าไปที่ http://หมายเลข IP เครื่อง/ จะต้องเจอหน้า Welcome ประมาณดังรูป

สั่งให้เปิด Nginx อัตโนมัติทุกครั้งที่บูตเครื่อง

systemctl enable nginx

ติดตั้ง NodeJS และ Express

ติดตั้ง NodeJS ด้วย yum เช่นเดิม (ได้ NPM พ่วงมาเลย)

yum install nodejs

เทสด้วยการพิมพ์

node -v

จะต้องขึ้นเลขเวอร์ชั่นมา

เทสว่า npm ติดตั้งหรือยัง ด้วย

npm -v

ต้องขึ้นเลขเวอร์ชั่นมาเช่นกัน

หากต้องการติดตั้ง Express ในโปรเจค ให้เข้าไปในโฟลเดอร์โปรเจคให้เรียบร้อย แล้วส่งคำสั่ง

npm install express

ถ้าไม่มี error อะไร แสดงว่าติดตั้ง Express สำเร็จ

ลองสร้างเว็บ A และ B

สร้างไฟล์ siteA.js ขึ้นมา (อาจใช้ nano หรือ vi ตามแต่สะดวก) เนื้อหาในไฟล์มีดังนี้

const http = require('http');
const port = 3000;
const ip = '0.0.0.0';

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World, this site A');
}).listen(port, ip);

console.log(`server is running on ${ip}:${port}`);

สร้างไฟล์ siteB.js ขึ้นมา โดยมีเนื้อหาในไฟล์ดังนี้

const http = require('http');
const port = 3001;
const ip = '0.0.0.0';

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World, this site B');
}).listen(port, ip);

console.log(`server is running on ${ip}:${port}`);

ทดสอบรันด้วย

node siteA

จะต้องไม่ error อะไร และขึ้น server is running on ….. ให้ทดสอบก่อนโดยเปิดหน้าต่าง SSH (หรือ Console) ขึ้นมาใหม่อีกหน้า แล้วรัน

curl localhost:3000

ต้องได้

Hello World, this site A

กลับมา

ตัวเว็บ B เช่นเดียวกัน พิมพ์ node siteB เพื่อรัน แล้วใช้ curl localhost:3001 เทส ต้องได้

Hello World, this site B

ถึงจะถูกต้อง

เมื่อถึงส่วนนี้ เว็บ A และเว็บ B จะเรียกจากภายในได้แล้ว เพื่อให้ง่ายในขั้นตอนต่อไป เราจะรันเว็บ A กับ B ค้างไว้ โดยพิมพ์คำสั่งต่อไปนี้

node siteA &
node siteB &

สังเกตว่ามี & ต่อหลังขึ้นมา คือเป็นการส่งคำสั่งแบบไม่ต้องรอคำสั่งนั้นทำงานจบนั่นเอง (ทำงานเป็นเบื้องหลัง)

ทำให้ Nginx วิ่งไปถูกที่

เข้าไปในโฟลเดอร์เก็บคอนฟิกของ nginx (ของผมอยู่ที่ /etc/nginx) แล้วเข้าไปในโฟลเดอร์ conf.d สร้างไฟล์ใหม่ดังนี้

สร้าง siteA.conf ขึ้นมา มีเนื้อหาภายในดังนี้

server {
	listen 80;
	server_name siteA.com; 

	location / {
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_pass http://127.0.0.1:3000;
		proxy_connect_timeout 90;
		proxy_send_timeout 90;
		proxy_read_timeout 120;
	}
}

สร้าง siteฺฺB.conf ขึ้นมา มีเนื้อหาภายในดังนี้

server {
	listen 80;
	server_name siteB.com; 

	location / {
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_pass http://127.0.0.1:3001;
		proxy_connect_timeout 90;
		proxy_send_timeout 90;
		proxy_read_timeout 120;
	}
}

แล้วเริ่มให้ Nginx รับ คอนฟิกใหม่ ด้วยคำสั่ง

systemctl restart nginx

แค่นี้เมื่อเรียกเว็บ A ก็จะได้เนื้อหาของเว็บ A มาแล้ว และเรียกเว็บ B ก็ได้ของเว็บ B มาแล้ว

แก้ไฟล์ hosts

เนื่องจากคอนฟิกของ nginx ได้ตั้งโดเมน siteA.com ไปที่เว็บ A และ siteB.com ไปที่เว็บ B ซึ่งทั้ง siteA.com และ siteB.com ไม่มีอยู่จริง จึงต้องแก้ไฟล์ hosts ขึ้นมาเพื่อทดสอบว่าสิ่งที่ทำมานั้น ทำงานได้หรือไม่

ใน Windows ให้เข้าไปที่ C:\Windows\System32\drivers\etc เปิดไฟล์ hosts ขึ้นมาด้วย text editor ตัวใดก็ได้ แล้วเพิ่มบรรทัดต่อไปนี้ลงไปท้ายไฟล์

หมายเลข IP ของเซิร์ฟเวอร์ siteA.com
หมายเลข IP ของเซิร์ฟเวอร์ siteB.com

แล้วบันทึกให้เรียบร้อย เมื่อเข้า siteA.com และ siteB.com จะขึ้นหน้าเว็บของ A และ B แล้ว

 

แค่นี้โปรเจคของเราก็แยกออกจากกัน แถมยังแยกโดเมนอีกด้วย !

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องที่ต้องการถูกทำเครื่องหมาย *