Đây là phần 2 của bài viết về cách deploy ứng dụng NodeJs lên VPS.
Cho phép kết nối HTTPS thông qua Firewall
Kiểm tra cấu hình hiện tại của ufw:
sudo ufw status
Chúng ta sẽ thấy output như dưới đây, nghĩa là chỉ có lưu lượng HTTP được phép tới web server:
Status: active
To Action From
-- ------ ----
Nginx HTTP ALLOW Anywhere
OpenSSH ALLOW Anywhere
Nginx HTTP (v6) ALLOW Anywhere (v6)
OpenSSH (v6) ALLOW Anywhere (v6)
Bây giờ để cho phép lưu lượng HTTPS, chúng ta cần phải cho phép cả profile Nginx Full và xóa quyền cho phép không cần thiết của profile Nginx HTTP. Chúng ta sẽ thực hiện như dưới đây:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
Sau đó kiểm tra lại status của ufw:
sudo ufw status
Chúng ta sẽ có output như dưới đây:
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
Cài đặt SSL Certificate
Certbot cung cấp nhiều cách để đạt được chứng chỉ SSL thông qua các plugin. Plugin Nginx sẽ tự động config lại Nginx và tải lại config khi cần thiết. Để sử dụng plugin này, chúng ta có thể chạy lệnh sau:
sudo certbot --nginx -d example.com -d example.com
- certbot là lệnh để tương tác với Certbot.
- -nginx chỉ định rằng chúng ta đang sử dụng plugin Nginx để tự động cấu hình lại Nginx.
- d example.com được sử dụng để chỉ định tên miền của chúng ta muốn đạt được chứng chỉ SSL cho nó. Có thể thêm nhiều tên miền bằng cách thêm thêm flag d other_domain
Khi lần đầu tiên chạy Certbot, chúng ta sẽ được yêu cầu nhập địa chỉ email và đồng ý với các điều khoản dịch vụ. Sau khi làm điều này, Certbot sẽ giao tiếp với máy chủ Let’s Encrypt để yêu cầu một chứng chỉ cho tên miền của chúng ta. Nếu thành công, sẽ nhận được output sau đây:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2024-06-11.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for example.com to /etc/nginx/sites-enabled/example.com
Congratulations! You have successfully enabled HTTPS on https://example.com
We were unable to subscribe you the EFF mailing list because your e-mail address appears to be invalid. You can try again later by visiting https://act.eff.org.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
Kiểm tra Certbot tự động làm mới chứng chỉ
Chứng chỉ của Let’s Encrypt chỉ có hiệu lực trong 90 ngày. Điều này nhằm khuyến khích người dùng tự động hóa quá trình làm mới chứng chỉ của họ. Gói certbot mà chúng ta đã cài đặt sẽ xử lý điều này bằng cách thêm một tập lệnh làm mới vào /etc/cron.d. Tập lệnh này sẽ chạy hai lần mỗi ngày và sẽ tự động làm mới bất kỳ chứng chỉ nào gần hết hạn trong vòng 30 ngày.
Để kiểm tra quá trình này, chúng ta có thể thực hiện một thử nghiệm "dry run" với certbot:
sudo certbot renew --dry-run
Lệnh này sẽ mô phỏng quá trình làm mới chứng chỉ mà không thực sự làm mới chúng. Điều này giúp chúng ta đảm bảo rằng quá trình làm mới được cấu hình đúng và sẵn sàng hoạt động khi chứng chỉ của chúng ta cần được làm mới.
Deploy Node.js app lên VPS Linux Ubuntu
Bây giờ chúng ta sẽ bắt đầu vào phần chính của bài viết này đó là deploy ứng dụng Node.js lên VPS Linux Ubuntu. Ở đây mình sẽ deploy một ứng dụng Node.js + Express.js đã có sẵn của mình ở trên Github, Chúng ta sẽ clone repo đó về và deploy.
Cài đặt Node.js bằng Node Version Manager
Các bạn chạy lần lượt các lệnh sau,
Chạy lệnh dưới đây để cài đặt nvm trước
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
Tiếp theo kích hoạt nvm chạy lệnh:
source ~/.bashrc
Bây giờ liệt kê các phiên bản của Node.js có sẵn để cài đặt thông qua nvm:
nvm list-remote
...
v19.9.0
v20.0.0
v20.1.0
v20.2.0
v20.3.0
v20.3.1
v20.4.0
v20.5.0
v20.5.1
v20.6.0
v20.6.1
v20.7.0
v20.8.0
v20.8.1
v20.9.0 (LTS: Iron)
v20.10.0 (LTS: Iron)
v20.11.0 (LTS: Iron)
v20.11.1 (Latest LTS: Iron)
v21.0.0
v21.1.0
v21.2.0
v21.3.0
v21.4.0
v21.5.0
v21.6.0
v21.6.1
v21.6.2
v21.7.0
v21.7.1
Chúng ta có thể cài đặt một phiên bản của Node bằng cách nhập bất kỳ phiên bản phát hành nào được liệt kê như ở trên. Ở đây mình sẽ cài phiên bản v20.11.1 (Latest LTS: Iron), chạy lệnh sau:
nvm install v20.11.1
Chúng ta có thể xem các phiên bản Node.js bạn đã cài đặt bằng cách chạy lệnh:
nvm list
> v20.11.1
default -> v20.11.1
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v20.11.1) (default)
stable -> 20.11 (-> v20.11.1) (default)
lts/* -> lts/iron (-> v20.11.1)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.12 (-> N/A)
lts/fermium -> v14.21.3 (-> N/A)
lts/gallium -> v16.20.2 (-> N/A)
lts/hydrogen -> v18.19.1 (-> N/A)
lts/iron -> v20.11.1
Các bạn có thể chuyển đổi giữa các phiên bản Node.js bằng lệnh:
nvm use v20.11.1
Thay v20.11.1 bằng phiên bản bạn muốn sử dụng.
Kiểm tra Git
Nếu các bạn clone project từ về bằng git thì chúng ta sẽ kiểm tra trước đã có sẵn git trên con VPS của mình chưa bằng cách chạy lệnh:
git --version
Nếu output ra version của Git thì bây giờ chúng ta sẽ sang bước tiếp theo. Nếu chưa thì các bạn cài Git bằng lệnh sau:
sudo apt install git-all
Clone ứng dụng Node.js
Đầu tiên mình sẽ chuyển tới thư mục home
cd /home
Sau đó sẽ clone project trên Github về:
git clone github_repo_url
Khi lần đầu tiên chạy git clone thì chúng ta sẽ được hỏi username và password (token) account Github. Các bạn hãy nhập user và password (token) account Github của các bạn để tiếp tục.
Sau khi clone project về, chúng ta sẽ di chuyển vào thư mục của project và cài đặt các dependencies:
cd myapp
npm install
Sau đó chúng ta sẽ chạy thử xem project của chúng ta đã run lên được chưa. Đây là nội dung file index.js của mình:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`App is listening on: http://localhost:${port}`)
})
Chạy lệnh dưới đây để khởi chạy app:
node index.js
Các bạn sẽ thấy output như sau:
App is listening on: http://localhost:3000
Cài đặt PM2
Tiếp theo, chúng ta sẽ cài đặt PM2, một trình quản lý tiến trình cho các ứng dụng Node.js. PM2 cho phép daemonize các ứng dụng để chúng sẽ chạy ở nền như một dịch vụ (service).
Sử dụng npm để cài đặt PM2 mới nhất trên máy chủ VPS của chúng ta:
npm install pm2@latest -g
Đầu tiên, chúng ta sẽ sử dụng lệnh pm2 start để chạy app của chúng ta, index,js ở dưới nền:
pm2 start index.js
Lệnh này sẽ khởi chạy app của chúng ta index.js bằng PM2 và chạy nó ở chế độ nền. Nó cũng thêm ứng dụng của chúng ta vào danh sách tiến trình của PM2, danh sách này sẽ được xuất ra mỗi khi chúng ta khởi động một ứng dụng. Điều này cho phép chúng dễ dàng quản lý các ứng dụng đang chạy bằng PM2.
-------------
__/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____
_\/\\\/////////\\\_\/\\\\\\________/\\\\\\__/\\\///////\\\___
_\/\\\_______\/\\\_\/\\\//\\\____/\\\//\\\_\///______\//\\\__
_\/\\\\\\\\\\\\\/__\/\\\\///\\\/\\\/_\/\\\___________/\\\/___
_\/\\\/////////____\/\\\__\///\\\/___\/\\\________/\\\//_____
_\/\\\_____________\/\\\____\///_____\/\\\_____/\\\//________
_\/\\\_____________\/\\\_____________\/\\\___/\\\/___________
_\/\\\_____________\/\\\_____________\/\\\__/\\\\\\\\\\\\\\\_
_\///______________\///______________\///__\///////////////__
Runtime Edition
PM2 is a Production Process Manager for Node.js applications
with a built-in Load Balancer.
Start and Daemonize any application:
$ pm2 start app.js
Load Balance 4 instances of api.js:
$ pm2 start api.js -i 4
Monitor in production:
$ pm2 monitor
Make pm2 auto-boot at server restart:
$ pm2 startup
To go further checkout:
http://pm2.io/
-------------
[PM2] Spawning PM2 daemon with pm2_home=/root/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/myapp/index.js in fork_mode (1 instance)
[PM2] Done.
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ index │ default │ 1.0.0 │ fork │ 140692 │ 0s │ 0 │ online │ 0% │ 38.1mb │ root │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Các ứng dụng đang chạy dưới PM2 sẽ được khởi động lại tự động nếu ứng dụng đó bị crash hoặc killed, nhưng chúng ta có thể thực hiện một bước bổ sung để cho ứng dụng được khởi chạy khi hệ thống khởi động bằng cách sử dụng lệnh con startup. Lệnh con này tạo ra và cấu hình một script khởi chạy để khởi động PM2 và các tiến trình được quản lý của nó khi hệ thống khởi động:
pm2 startup systemd
Chúng ta sẽ được output như dưới đây:
[PM2] Init System found: systemd
Platform systemd
Template
[Unit]
Description=PM2 process manager
Documentation=https://pm2.keymetrics.io/
After=network.target
[Service]
Type=forking
User=root
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/root/.nvm/versions/node/v20.11.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/root/.pm2
PIDFile=/root/.pm2/pm2.pid
Restart=on-failure
ExecStart=/root/.nvm/versions/node/v20.11.1/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/root/.nvm/versions/node/v20.11.1/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/root/.nvm/versions/node/v20.11.1/lib/node_modules/pm2/bin/pm2 kill
[Install]
WantedBy=multi-user.target
Target path
/etc/systemd/system/pm2-root.service
Command list
[ 'systemctl enable pm2-root' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-root...
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-root.service → /etc/systemd/system/pm2-root.service.
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save
[PM2] Remove init script via:
$ pm2 unstartup systemd
Dưới đây là một số điểm chính của script khởi động được tạo:
- Script khởi động được lưu tại đường dẫn: /etc/systemd/system/pm2-root.service.
- PM2 sẽ được chạy dưới quyền root.
- Script được cấu hình để khởi động PM2 bằng phiên bản Node.js được quản lý bởi NVM (/root/.nvm/versions/node/v20.11.1).
- Khi hệ thống khởi động lại, script này sẽ tự động được kích hoạt để khởi động PM2.
- pm2 save sẽ lưu trữ thông tin về các quy trình PM2 đang quản lý để có thể khôi phục lại sau khi hệ thống khởi động lại.
- pm2 unstartup systemd được sử dụng để gỡ bỏ script khởi động của PM2 từ systemd. Khi chúng ta chạy lệnh này, PM2 sẽ gỡ bỏ script khởi động systemd đã được tạo trước đó để PM2 không còn tự động khởi động cùng với hệ thống.
Ở đây chúng ta sẽ chạy lệnh pm2 save để lưu lại file script khởi động này:
pm2 save
Bây giờ chúng ta sẽ start dịch vụ này bằng systemctl:
systemctl start pm2-root
Sau đó kiểm tra lại status:
systemctl status pm2-root
Chúng ta sẽ được output như dưới đây:
● pm2-root.service - PM2 process manager
Loaded: loaded (/etc/systemd/system/pm2-root.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2024-03-18 14:57:01 UTC; 32s ago
Docs: https://pm2.keymetrics.io/
Process: 145513 ExecStart=/root/.nvm/versions/node/v20.11.1/lib/node_modules/pm2/bin/pm2 resurrect (code=exited, status=0/SUCCESS)
Main PID: 140675 (PM2 v5.3.1: God)
Tasks: 0 (limit: 2237)
Memory: 4.0K
CPU: 688ms
CGroup: /system.slice/pm2-root.service
‣ 140675 "PM2 v5.3.1: God Daemon (/root/.pm2)" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
Mar 18 14:57:00 lahocore systemd[1]: Starting PM2 process manager...
Mar 18 14:57:01 lahocore pm2[145513]: [PM2] Resurrecting
Mar 18 14:57:01 lahocore pm2[145513]: [PM2] Restoring processes located in /root/.pm2/dump.pm2
Mar 18 14:57:01 lahocore pm2[145513]: ┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
Mar 18 14:57:01 lahocore pm2[145513]: │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
Mar 18 14:57:01 lahocore pm2[145513]: ├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
Mar 18 14:57:01 lahocore pm2[145513]: │ 0 │ index │ default │ 1.0.0 │ fork │ 140692 │ 13m │ 0 │ online │ 0% │ 51.7mb │ root │ disabled │
Mar 18 14:57:01 lahocore pm2[145513]: └────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Mar 18 14:57:01 lahocore systemd[1]: Started PM2 process manager.
Dưới đây là các lệnh của PM2 để các bạn có thể quản lý ứng dụng của mình:
Dừng một ứng dụng trong PM2:
pm2 stop
Khởi động lại một dứng dụng:
pm2 restart app_name_or_id
Xem danh sách ứng dụng đang chạy trong PM2:
pm2 list
Xem thông tin ứng dụng:
pm2 info app_name