Node.js là công cụ hàng đầu để tạo ứng dụng server bằng JavaScript, cung cấp cả chức năng của web server và application server.

Tuy nhiên, Node.js có một số hạn chế và lỗ hổng có thể gây ra các vấn đề về hiệu suất và thậm chí là sự cố hệ thống. Ví dụ, các ứng dụng web dựa trên Node dễ bị chậm trong việc thực thi mã và gặp sự cố do các hoạt động IO-bound hoặc do tăng trưởng lưu lượng truy cập nhanh chóng. Node.js cũng đôi khi gặp khó khăn trong việc phục vụ nội dung tĩnh như hình ảnh và tệp JavaScript cũng như cân bằng tải giữa các server.

May mắn thay, chúng ta có thể cache nội dung tĩnh, reverse proxy và cân bằng tải giữa nhiều application server, và quản lý tranh chấp cổng giữa các client bằng cách sử dụng Nginx. Điều này làm cho Nginx trở thành một công cụ tuyệt vời để tăng hiệu suất của Node.js.

Trong hướng dẫn này, Tôi sẽ chỉ các bạn cách reverse proxy(Đọc thêm bài viết về proxy để hiểu hơn) một ứng dụng Node.js với Nginx. Chúng ta sẽ xây dựng một ứng dụng Node.js đơn giản chạy trên port 3000 và sử dụng Nginx như một reverse proxy server cho ứng dụng Node.js. Ứng dụng sẽ được truy cập thông qua một domain name:

Để tiện theo dõi bài viết, bạn cần chuẩn bị:

  • Hiểu biết về cách web, web server và web browser giao tiếp với nhau
  • Kiến thức cơ bản về JavaScript, Node.js, lập trình bất đồng bộ (asynchronous programming), mạng máy tính (computer networking) và DNS
  • Một máy ảo Ubuntu(VPS) của một trong các nhà cung cấp AWS, Google…, mở các port 22, 80, 443.
  • Node.js đã được cài đặt trên máy chủ của bạn
  • Một domain name từ một DNS registrar
  • Trình soạn thảo (vim, nano) đã được cài đặt máy chủ của bạn

Server và web server là gì?

Server là một máy tính giao tiếp với các máy tính khác để cung cấp thông tin theo yêu cầu của các máy tính này. Các máy tính này, còn được gọi là client, kết nối với server thông qua mạng cục bộ (LAN) hoặc mạng diện rộng (WAN).

Web server là một server trên internet sử dụng giao thức HTTP (Hypertext Transfer Protocol) để nhận các request từ client, chẳng hạn như trình duyệt. Sau đó nó trả về một HTTP response, có thể là một trang HTML hoặc dữ liệu ở định dạng JSON, như được sử dụng trong các API call.

Web server, cần thiết cho việc trao đổi dữ liệu, sử dụng HTTP để giao tiếp client-server. Chúng bao gồm cả phần cứng và phần mềm, rất quan trọng trong phát triển web. Phần mềm chịu trách nhiệm giải mã URL và quản lý quyền truy cập của người dùng vào các tệp được lưu trữ.

Nginx là gì?

Theo tài liệu, Nginx (đọc là “engine X”) là một HTTP và reverse proxy server, mail proxy server, và proxy server TCP/UDP chung, ban đầu được viết bởi Igor Sysoev.

Nginx được sử dụng cho nhiều tác vụ khác nhau giúp cải thiện hiệu suất của Node:

  • Reverse proxy server: Khi lưu lượng truy cập vào ứng dụng tăng lên, cách tốt nhất để cải thiện hiệu suất là sử dụng Nginx làm reverse proxy server phía trước Node.js server để cân bằng tải giữa các server. Đây là trường hợp sử dụng chính của Nginx trong các ứng dụng Node.js.
  • Stateless load balancing: Tính năng này cải thiện hiệu suất và giảm tải cho các dịch vụ backend bằng cách chuyển các yêu cầu client đến bất kỳ server nào có quyền truy cập vào tệp được yêu cầu.
  • Cache static contents: Phục vụ nội dung tĩnh trong một ứng dụng Node.js và sử dụng Nginx làm reverse proxy server có thể tăng gấp đôi hiệu suất của ứng dụng lên tới 1,600 yêu cầu mỗi giây.
  • Implement SSL/TLS và HTTP/2: Với sự chuyển đổi gần đây sang việc sử dụng SSL/TLS để bảo mật các tương tác của người dùng trong ứng dụng Node.js, Nginx cũng hỗ trợ các kết nối HTTP/2.
  • Performance tracking: Bạn có thể theo dõi hiệu suất tổng thể của ứng dụng Node.js trong thời gian thực bằng các chỉ số trên bảng điều khiển trực tiếp của Nginx.
  • Scalability: Tùy thuộc vào loại tài nguyên bạn đang phục vụ, bạn có thể tận dụng các tính năng load balancing HTTP, TCP, và UDP đầy đủ của Nginx để mở rộng ứng dụng Node.js.

Nginx hiện hỗ trợ bảy ngôn ngữ lập trình(Tính đến lúc tôi viết bài này): Go, Node.js, Perl, PHP, Python, Ruby, và Java Servlet Containers (đây là một module thử nghiệm). Nó cho phép bạn chạy các ứng dụng viết bằng các ngôn ngữ khác nhau trên cùng một server.

Bây giờ, chúng ta sẽ thiết lập ứng dụng Node.js của mình.

Tạo một Node.js application

Đối với ứng dụng Node.js đơn giản này, chúng ta sẽ xây dựng một máy chủ Node.js với mô-đun HTTP do Node.js cung cấp. Hãy bắt đầu bằng cách tạo một thư mục và khởi tạo dự án trên terminal:

mkdir nginx_server_project
cd nginx_server_project
npm init -y

Đoạn mã trên sẽ tạo thư mục nginx_server_project và chuyển vào thư mục đó. Sau đó, chúng ta khởi tạo một ứng dụng Node.js với npm, sử dụng cờ -y để đặt câu trả lời mặc định là “yes” cho tất cả các câu hỏi.

Bước tiếp theo là tạo tệp server.js chứa mã nguồn cho ứng dụng của chúng ta. Mở nó bằng bất kỳ IDE hoặc trình soạn thảo văn bản nào bạn chọn:

touch server.js
nano server.js

Bây giờ là lúc để xây dựng và khởi động server. Hãy định nghĩa hai subdomain bổ sung để kiểm tra rằng ứng dụng của chúng ta hoạt động hoàn toàn ổn định:

const http = require("http");
const server = http.createServer((req, res) => {
  const urlPath = req.url;
  if (urlPath === "/overview") {
    res.end('Welcome to the "overview page" of the nginX project');
  } else if (urlPath === "/api") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(
      JSON.stringify({
        product_id: "xyz12u3",
        product_name: "NginX injector",
      })
    );
  } else {
    res.end("Successfully started a server");
  }
});
server.listen(3000, "localhost", () => {
  console.log("Listening for request");
});

Chúng ta đã tạo một server với module HTTP của Node.js được nhập vào bằng hàm require trong đoạn code trên. Trong server, chúng ta sẽ hiển thị hai response khác nhau tùy thuộc vào route hiện tại. Hai route này là /overview/api.

Trên subdomain /overview, chúng ta sẽ hiển thị một đoạn text thuần túy, trong khi trên /api, chúng ta sẽ hiển thị một đối tượng JSON. Ứng dụng trên sẽ được truy cập qua địa chỉ Public IPv4 của máy ảo của bạn — ví dụ, 34.211.115.4 trên port 3000.

Bây giờ khi ứng dụng Node server đã sẵn sàng, hãy cài đặt Nginx và cấu hình nó.

Cài đặt Nginx

Chúng ta sẽ cài đặt Nginx bằng trình quản lý gói mặc định cho hệ điều hành dựa trên Debian, gọi là apt. Nginx cũng có sẵn trên hầu hết các hệ điều hành trong các kho lưu trữ mặc định của chúng.

Trước khi cài đặt Nginx, hãy chắc chắn rằng bạn đã cài đặt các yêu cầu cần thiết cho hệ điều hành Ubuntu. Tiếp theo, chúng ta sẽ cấu hình Nginx dựa trên các yêu cầu cụ thể của dự án, và sau đó sẵn sàng triển khai.

Cấu hình Nginx

Để Nginx có thể chuyển hướng đến ứng dụng Node.js đang lắng nghe trên port 3000, trước tiên chúng ta cần bỏ liên kết cấu hình mặc định của Nginx và sau đó tạo một cấu hình mới để sử dụng cho ứng dụng Node.js của chúng ta.

Để bỏ liên kết cấu hình mặc định của Nginx, chúng ta có thể sử dụng lệnh sau:

sudo unlink /etc/nginx/sites-available/default

Cấu hình Nginx được lưu trữ trong thư mục /etc/nginx/sites-available. Để tạo một cấu hình mới, chúng ta hãy chuyển đến thư mục này và tạo một tệp cấu hình trỏ đến server Node.js của chúng ta:

cd /etc/nginx/sites-available
touch myserver.config

Sau khi thay đổi thư mục đến /etc/nginx/sites-available, lệnh thứ hai sẽ tạo một tệp cấu hình Nginx có tên myserver.config.

Tiếp theo, mở file myserver.config

sudo nano /etc/nginx/sites-available/myserver.config

Viết code theo mẫu sau:

#The Nginx server instance
server{
    listen 80;
    server_name wach.quest;
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # location /overview {
        #     proxy_pass http://127.0.0.1:3000$request_uri;
        #     proxy_redirect off;
        # }
    }
}

Cấu hình trên yêu cầu Nginx lắng nghe trên cổng 80 của your-domain.com. Dấu / là Uniform Resource Identifier (URI) của chúng ta với các thuộc tính sau:

  • proxy_set_header: Thiết lập header host thành header của máy chủ Nginx.
  • proxy_pass HTTP: Chỉ dẫn Nginx chuyển tiếp tất cả các request phù hợp với mẫu location đến một upstream server (máy chủ backend)
  • proxy_http_version: Chuyển đổi kết nối đến HTTP 1.1.
  • proxy_set_header Upgrade: Chuyển đổi kết nối proxy thành loại Upgrade vì WebSockets chỉ giao tiếp trên các kết nối đã nâng cấp.
  • proxy_set_header Connection: Đảm bảo giá trị header kết nối là Upgrade.

Lưu các thay đổi và thoát tệp bằng cách nhấn tổ hợp phím ctrl + x. Sau đó có 1 hộp thoại hỏi bạn có muốn lưu các thay đổi hay không bạn chọn yes(y).(lưu ý đây là tôi đang sử dụng trình soạn thảo nano)

Tiếp theo, hãy kích hoạt file trên bằng cách tạo một symbolic link từ nó đến thư mục sites-enabled, nơi mà Nginx đọc trong quá trình khởi động:

sudo ln -s /etc/nginx/sites-available/myserver.config /etc/nginx/sites-enabled/

Server hiện đã được kích hoạt và cấu hình để trả về các response cho các request dựa trên port listen và đường dẫn location.

Bây giờ đã đến lúc khởi động cả Node.js application và dịch vụ Nginx để kích hoạt các thay đổi gần đây. Nhưng trước tiên, hãy kiểm tra trạng thái của Nginx để xác nhận rằng cấu hình đang hoạt động đúng cách:

sudo nginx -t

Đầu ra khi chạy lệnh trên sẽ trông như thế này:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Kết quả đầu ra ở trên xác nhận rằng cấu hình của chúng ta đã thành công. Tiếp theo, khởi động lại Nginx để kích hoạt các thay đổi của bạn:

sudo systemctl restart nginx

Khi Nginx khởi động lại, hãy cho phép truy cập đầy đủ thông qua firewall của Nginx:

sudo ufw allow 'Nginx Full'

Tiếp theo, điều hướng đến thư mục của ứng dụng Node.js:

cd ~/nginx_server_project

Khởi động ứng dụng máy chủ Node.js bằng lệnh sau:

node server.js

Mở trình duyệt và truy cập vào ứng dụng Node.js sử dụng your-domain.com:

Tiếp theo, trên trình duyệt chúng ta truy cập vào your-domain.com/overview.

Để kiểm tra xem các đường dẫn còn lại của node.js server của chúng ta có hoạt động hay không, trên trình duyệt chúng ta truy cập vào your-domain.com/api.

Tùy chọn cấu hình Nginx nâng cao

Node.js đã trở nên phổ biến trong việc tạo ra các application online nhanh và có khả năng mở rộng. Tuy nhiên, việc tối ưu hóa cơ sở hạ tầng web server là điều cần thiết để tận dụng các khả năng của nó. Phần này sẽ khám phá cách tối ưu hóa khả năng mở rộng và bảo mật của các ứng dụng Node.js bằng cách đề cập đến các tùy chọn cấu hình Nginx nâng cao bao gồm SSL termination, load balancing và nhiều tính năng khác.

1. SSL termination với Nginx

Việc bảo mật giao tiếp giữa các client và server Node.js là rất quan trọng, và mã hóa SSL/TLS là một thành phần chủ chốt để thực hiện điều này. Nginx hoạt động tốt khi thực hiện SSL termination.

Khi Nginx xử lý việc mã hóa HTTPS từ các client, nó giúp giảm bớt công việc mã hóa SSL/TLS cho các server web và ứng dụng phía sau (upstream).

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /path/to/ssl_certificate.crt;
    ssl_certificate_key /path/to/ssl_certificate.key;
    # SSL settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    location / {
        proxy_pass http://node_app_servers;
        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;
    }
}

Giải thích file cấu hình trên:

  • Cấu hình Nginx để lắng nghe trên cổng 443 cho các kết nối SSL
  • Chỉ định đường dẫn chứng chỉ SSL và khóa cho việc xử lý SSL, để lấy được chứng chỉ SSL chúng ta có thể mua của một doanh nghiệp chuyên cung cấp hoặc bạn có thể sử dụng chứng chỉ của Let’s Encrypt hoàn toàn miễn phí.Ở phần 2 tôi sẽ hướng dẫn bạn lấy chứng chỉ của Let’s Encrypt hoàn toàn miễn phí.
  • Cấu hình các giao thức và bộ mã hóa SSL để đảm bảo giao tiếp an toàn
  • Sử dụng chỉ thị proxy_pass để chuyển tiếp các yêu cầu đến các server Node.js phía sau

2. Load balancing với Nginx

Khi các ứng dụng Node.js phát triển về quy mô để đáp ứng số lượng người dùng ngày càng tăng, việc phân bổ lưu lượng đến nhiều server backend trở nên quan trọng để đảm bảo hiệu quả và độ tin cậy.

Nginx cung cấp nhiều thuật toán để hỗ trợ việc cân bằng tải. Các thuật toán này xác định cách phân phối tải giữa các server có sẵn:

http {
  upstream node_app_servers {
      # no load balancing method is specified for Round Robin
      server api1.backend.com;
      server api2.backend.com;
  }
  server {
      listen 80;
      server_name example.com;
      location / {
          proxy_pass http://node_app_servers;
          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;
      }
  }
}
  • Directive upstream xác định một nhóm các Node.js server backend cho load balancing
  • Nginx phân phối các request đến nhiều server được chỉ định bằng thuật toán, nếu chúng ta không chỉ định thì thuật toán round-robin là mặc định.Tìm hiểu thêm về setup chi tiết thuật toán cho load balancing tại đây
  • Directive proxy_pass chuyển tiếp các request đến các server backend trong nhóm upstream(node_app_servers)

3. Caching và phân phối nội dung(content delivery) với Nginx

Hiệu suất và khả năng mở rộng của các ứng dụng Node.js có thể được cải thiện bằng cách tối ưu hóa tài nguyên tĩnh và nội dung động để lưu trữ trong bộ nhớ đệm (caching).

Bằng cách lưu trữ nội dung dựa trên các URL pattern, response header và các tiêu chí khác, Nginx giúp các Node.js server dễ dàng hơn trong việc phục vụ nội dung một cách động(dynamically). Điều này giải phóng tài nguyên cho các logic liên quan đến application. Để phản hồi các request từ client mà không cần yêu cầu proxy request cho cùng một nội dung mỗi lần, Nginx sử dụng disk caching để lưu trữ các response cho các truy vấn:

http {
    # ...
    proxy_cache_path /data/nginx/cache keys_zone=mycache:10m loader_threshold=300
                     loader_files=200 max_size=200m;
    server {
        listen 8080;
        proxy_cache mycache;
        location / {
            proxy_pass http://backend1;
        }
        location /some/path {
            proxy_pass http://backend2;
            proxy_cache_valid any 1m;
            proxy_cache_min_uses 3;
            proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;
        }
    }
}

Để đọc thêm về nội dung này vui lòng truy cập vào đây

4. Tăng cường bảo mật với Nginx

Nginx cung cấp các khả năng bảo mật mạnh mẽ để bảo vệ các Node.js application khỏi các cuộc tấn công và lỗ hổng, bên cạnh việc nâng cao khả năng mở rộng.

Bởi vì Nginx có số lượng lớn các module bảo mật, nó cung cấp các tính năng như rate limiting, access control và request filtering để ngăn chặn các cuộc tấn công như SQL injection, DDoS và cross-site scripting:

server {
    listen 80;
    server_name example.com;
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=limit_zone:10m rate=10r/s;
    limit_req zone=limit_zone burst=20;
    # Basic access control
    allow 192.168.1.0/24;
    deny all;
    # Request filtering
    if ($request_uri ~* (\.git|\.svn)) {
        return 404;
    }
  # ...other configurations
}

Thực ra phần kiến thức nâng cao về nginx các bạn có thể đọc hết ở website chính thức của nginx.

Trong phần này, chúng ta đã học cách thiết lập Nginx làm web server cho Node.js backend. Node.js bây giờ chỉ đóng vai trò là app server để giải quyết các logic. Sau đó, chúng ta cấu hình Nginx để lắng nghe cổng 3000 và phục vụ các nội dung đã được định nghĩa trước trong app server Node.js của chúng ta trên trình duyệt.Cám ơn đã đọc bài viết này.Có bất kỳ câu hỏi nào các bạn vui lòng bình luận mình sẽ cố hết sức giải đáp.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Categories

Quote

“Ta nhẹ nhàng đi cũng như khi ta nhẹ nhàng đến, ta vẫy tay chào không một chút vấn vương”

~Tào Tháo

Designed with WordPress