iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 8
0
Modern Web

前端工程師一起來種一棵後端技能樹吧!系列 第 8

[Day 08] Web Server & Nginx — (2)

上一篇文章中,我們了解到 web server 究竟是什麼黑魔法,可以做到什麼事,還有與程式語言跑的 application server 有什麼差異,此篇文章則是要來介紹非常火紅的高效能 web server — Nginx。

Nginx 是什麼?它為什麼火紅?

Nginx (讀作 engine x),是一個免費的開源軟體,一個非同步框架的 web server,不過它的功用遠不僅止於 web server,它更多的用途是作為反向代理、Http Cache、負載平衡器。它在近幾年變得十分火熱,與較老牌的 Apache 成為 web server 的兩大巨頭,並且在較新的軟體專案中,使用率有超越 Apache 的趨勢。

了解到 Nginx 是什麼後,我們也得去了解一下它能夠走紅的原因,與它的優勢到底是什麼。

面向效能

經過詳細效能上的比較後(當然不是我去比較的),發現 Nginx 在處理 IO 並發與靜態文檔方面效能是比 Apache 還要好的,甚至是遠遠超越,原因必須追溯到兩種 web server 底層設計的差異,因為超過討論範圍,就不特別探討了。因此在需要應對大流量的狀況下,許多公司會選擇使用 Nginx 作為 server 而不是 Apache。

設置簡易

相比於 Apache, Nginx 因為模組化的設計,配置上是較為簡單的,不過就提供的功能與相關套件來看,則是歷史悠久的 Apache 略勝一籌。

記憶體消耗低

一樣拿 Apache 來比較,Nginx 佔用了相對低的資源與內存,另外值得注意的是 Nginx 在高併發狀況下可以保持低資源低消耗,卻仍能保有高效能。

反向代理&負載平衡

上篇文章中也介紹過這兩個概念,其實 Apache 當然也是做得到的,差別就體現在上面的第一點:效能。同樣實作這兩個功能,Nginx 可以消耗更少的資源,這也代表著在相同資源的狀況下,Nginx 可以處理更多的連線請求。

...

了解完 Nginx 的優勢後,就來看看怎麼設置它吧!

安裝 Nginx

一般狀況會在 server(AWS EC2、Digital Ocean) 安裝 Nginx,而 server 的作業系統環境大多數則為 Linux,這時可以透過以下指令安裝 Nginx (以 Ubuntu 為例):

Step 1 : 更新 apt-get 套件內容

sudo apt-get update

Step 2 : 安裝 Nginx

apt-get install nginx

除了直接下載整包事先包好的 Nginx 以外,Nginx 還提供另一種 Compiling and Installing from Source 的下載方式,讓開發者可以彈性的加入自己需要的功能,詳情請看官網文件。

當然也可以用最近同樣也十分火紅的 Docker 來設置 Nginx:

官方提供了 Nginx 的 image,可以直接使用

docker run -d -p 7777:80 --name nginx-web-server nginx

Nginx 的 Config 檔

Nginx 的功能如上一篇文章提到的負載平衡都是透過 config file 來設定的。

(以下內容均假設讀者擁有一台 Linux Server )

Nginx 的主要設定檔通常會放置在 /etc/nginx/nginx.conf
另外在 /etc/nginx/conf.d/*.conf 則會放置不同域名的 config file
例如: /etc/nginx/conf.d/kylemocode.com.conf
然後在主設定檔中的 http context 加入一行

include /etc/nginx/conf.d/*.conf;

即可將不同域名的設定引入,達成方便管理與修改不同域名設定的特性。

接下來我們試著在 /etc/nginx/conf.d/ 建立 default.conf 的設定檔,來為 Nginx Server 建立基礎的配置吧。

default.conf

upstream api {
    server localhost:5000;
    server localhost:5001;
}

server {
    listen 80;
    listen [::]:80;
    server_name SERVER_IP;
    root /home/ryan;

    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
            proxy_pass http://api/;
    }
}

直接看上面的設定檔可能讓讀者一頭霧水了吧?沒關係,接下來我們將一個個 block 拆出來講解。

upstream

upstream block 定義了我們想要將 request proxy 過去的應用,上圖中的例子代表我們可以將請求 proxy 到分別監聽 5000 與 5001 port 的兩個應用,聰明的讀者應該猜到了,這個 block 可以讓我們達到 load balancer 負載平衡的功能。

server

這個 block 則是定義了 proxy server 的相關設定,包括要監聽的 port (http 為 80 ,https 為 443)、規定哪些 domain 或 ip 的 request 會被 nginx server 處理(server_name)。

location

這個 block 非常重要,不過幸好概念還蠻易懂的,它其實就像是 routing 的概念,設定不同的 path 要對應到怎麼樣的設定。上圖的範例 location 後面接的是 /,代表任何路徑都會被這個 block 給接收處理。

(location 後面也是可以吃 regex 的喔,例如:location ~* .(pl|cgi|perl|prl)$ {})

...

其實 Nginx conf 的設定真的太千變萬化了,絕非一篇文章可以講解完的,因此這邊只列出最最最基本的 block,其他設定讀者可以依照需求再去做查詢囉!

接下來想針對 gzip、https 與 load balancer 這三個例子示範 conf 的撰寫,以簡單的範例加深讀者的印象與理解,也證實 Nginx 的多工與強大。

gzip

gzip 其實就是在做壓縮(compression),減少 payload 或者檔案的大小以增進傳輸的效率。其實像是我熟悉的 express 或是 koa 這兩個 node.js 的 web framework 都有執行壓縮的 middleware,例如:

...
const compression = require('compression');
const app = express()
app.use(compression());
...

不過外界有一說法是壓縮是一個極其耗費資源的行為,對特性為 single thread 的 Node.js 來說會是一種負擔,因此壓縮的任務其實可以交給 Nginx Web Server 去完成。

server {
...
...

    gzip on;
    gzip_types text/plain application/xml application/json;
    gzip_comp_level 9;
    gzip_min_length 1000;

...
...
}

要在 Nginx 啟用 gzip 其實非常簡單,只需在 conf 加幾行設定就可以啟動最基本的 gzip 行為了。

https

因為安全性的考量與資安意識的提升,現在幾乎大部分的網站都會使用 https 了,而要將 http 變為 https ,Nginx 居然也可以做到。

不過要開啟 https ,是必須取得 SSL certificate 的,這個範例中我選擇使用 certbot 這個 package 取得來自 Let’s Encrypt 的免費憑證(有效期限 90 天)

sudo yum install certbot

certbot certonly --standalone -d DOMAIN_NAME

完成指令後 certificate 與 private key 會被建立在 /etc/letsencrypt/live/DOMAIN_NAME/ 路徑中,在接下來的 Nginx Conf 中會引入它們。

server {
    listen 80;
    listen [::]:80;
    server_name DOMAIN_NAME;
    return 301 https://$host$request_uri;
}

  server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name DOMAIN_NAME;
    root /home/ryan;

    ...
    ...

    ssl_certificate /etc/letsencrypt/live/DOMAIN_NAME/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/DOMAIN_NAME/privkey.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
    ssl_prefer_server_ciphers on;

    add_header Strict-Transport-Security max-age=15768000;

    resolver 8.8.8.8;
    
    ...
    ...
}

細節我就不多說明了,值得注意的是我們將來自 80 port (http) 的 request 重新導回 https 的 block,這也就是為什麼某些網站你輸入的是 http,它還是會自動幫你導回 https 的原因。

(google.com 就有這個行為,有興趣的讀者可以試試)

load balancer

upstream api {
        ip_hash;
        server localhost:5000;
        server localhost:5001;
}

server {
    listen 80;
    listen [::]:80;
    server_name SERVER_IP;
    root /home/ryan;

    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location /socket.io/ {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_pass http://nodejs/socket.io/;
    }

    location / {
            proxy_pass http://api/;
    }
}

這個範例就單純多了,要達到 load balancer 透過一開始介紹的 upstream block 就可以達成,在上面的例子中,來自某個 domain 80 port 的任何 path 都會被分配到 port 5000 或 port 5001 兩個應用中,達成用兩個應用去分擔 request 的負載平衡器。

你以為負載平衡的介紹就這樣結束了嗎?當然還沒!

你有沒有想過,那負載的規則是什麼?眼尖的讀者可能也會發現,upstream block 裡面的 ip_hash 又是什麼?

其實某個 request 要被導倒哪個應用去處理是有不同規則的,而每個規則都有各自適合使用的時機,以下簡單介紹幾個較常見的規則:

round-robin:(預設)輪詢方式

也就是將請求輪流按照順序分配給每一個 server。假設所有伺服器的處理效能都相同,不關心每臺伺服器的當前連線數和響應速度。適合於伺服器組中的所有伺服器都有相同的軟硬體配置並且平均伺服器請求相對均衡的情況。

不過也有另外一種可以設定權重的 Weight Round Robin (加權輪詢方式),可以設定不同 server 的權重,例如以下範例:

upstream myweb {
    server web1.dtask.idv.tw weight=3;
    server web2.dtask.idv.tw weight=2;
}

least-connected: 最少連線

顧名思義,連線進來時會把 Request 導向連線數較少的 Server。

IP-hash:依據 Client IP 來分配到不同台 Server

通過一個雜湊(Hash)函式將一個 IP 地址對映到一臺伺服器。先根據請求的目標 IP 地址,作為雜湊鍵(Hash Key)從靜態分配的散列表找出對應的伺服器。除非斷線或 IP變動,否則同個 IP 的請求都會導入到同一個 server。

除了 Load balancer 、gzip 、https 以外,Nginx 真的還有太多可以玩的了,例如調整 worker_process 的數量以調整效能或是將 request redirect 等等,有興趣的讀者可以自行探究。

小結

本篇文章介紹了 Nginx 是什麼,它的特性,與 Apache 的比較,還有如何安裝 Nginx、如何撰寫 conf 檔。最後也用三個例子證明 Nginx 的多功能性與強大,希望讀者能有所收穫,建立起探索 Nginx 世界的好奇心與動力!

medium 版本

https://medium.com/@oldmo860617/web-server-nginx-2-bc41c6268646

團隊成員系列文

想盡辦法當好一個Junior Backend Developer
用舒服的姿勢開發 Python Project


上一篇
[Day 07] Web Server & Nginx — (1)
下一篇
[Day09] 進程、線程、協程,傻傻分得清楚!
系列文
前端工程師一起來種一棵後端技能樹吧!30

1 則留言

1
maxam
iT邦新手 5 級 ‧ 2020-09-21 23:56:00

推個 線上 gen nginx config 的工具
p.s. 原作者本來只是當 side project 在維護,最後被 digital ocean 買下

感謝 maxam 大的補充/images/emoticon/emoticon08.gif

我要留言

立即登入留言