系列目錄:
本文的前提需要了解一些基礎的Linux知識。
以下圍繞 Nginx 1.23 的閘道器應用;參考官網:http://nginx.org/
本文沒有對概念性方面做深入的闡述,常見的是一筆帶過,而更多的...是對設定項的解釋。
通常後臺提供了不同的應用服務,甚至是叢集,每種服務每個服務,需要維護不同的請求地址,甚至服務認證、跨域等動作,管理起來比較麻煩。因此,需要一個閘道器,介於使用者端和應用服務之間,所有的外部請求都會先經過閘道器,閘道器再把請求分發到目標服務。閘道器對外提供唯一請求入口,作為對外聯絡的視窗,易於管理和維護請求。
作者:[Sol·wang] - 部落格園,原文出處:https://www.cnblogs.com/Sol-wang/
Nginx 不僅是一個高效能的Web伺服器,還具備存取代理、負載均衡、內容快取等功能,用於使用者端存取流量到後臺應用伺服器負載均衡和請求轉發。其基於模組化的程式碼架構及可與其它有效整合的可程式化特性,使其具有強大的擴充套件能力。Nginx以資源消耗低、高穩定、高效能的並行處理能力著稱。
存取代理:
Nginx 可以通過存取路徑、URL 關鍵字、使用者端 IP等多種手段實現存取路由分配。
反向代理:
將接收到的請求再轉到後端的目標應用伺服器,並把響應資料返回給使用者端。支援目前絕大多數的網路協定:HTTP/FastCGI/RPC/UDP/TCP等。
負載均衡:
通過自身的 upstream 模組支援多種負載均衡演演算法,使後端伺服器可以非常方便地進行橫向擴充套件,以應對高並行。
內容快取:
Nginx支援靜態站點和後端分離,可以把靜態內容快取起來,也可以將後端變化不大的響應結果快取起來,使整體實現了更高速的相應能力。
可延伸性:
可客製化的模組化架構方式,更多的語言(C/Perl/JavaScript/Lua)支援開發第三方模組並引入,增強可程式化及擴充套件能力。
首先,程序是CPU管理的執行單元,CPU的單個核心也可以執行多個程序,只不過是交替執行著各個程序,稱為時間片,這種方式速度很快,以至於看上去像在同時執行;多核CPU就能同時執行更多的程序。
Nginx是由多個程序執行,一個主程序Master和多個子程序Worker,主程序負責管理子程序,如:重啟/過載/建立/銷燬等,子程序負責處理具體的請求等業務功能。程序間共用記憶體資料,更多的程序帶來更好的處理能力。
Nginx 程序執行示意圖:
Nginx支援設定資訊的過載,並以最新的設定內容執行,當Nginx在高速執行的時候,如何做到平穩過渡呢?
相關命令:nginx -s reload
過載過程:
Nginx Master process 負責 fork 出一個新的 Worker process,最新的Worker使用新的設定資訊執行,這時候就銷燬一箇舊的worker,此時,Worker有新舊之分,新Worker用新設定執行,舊Worker依然用舊設定執行,Master繼續fork出新的Worker。。。以同樣的方式持續替換舊Worker,直到全部替換完成。整個過程中,Nginx 並沒有停止執行,絲滑過渡。
安裝前提:
yum install gcc -y # C語言編譯器
yum install pcre pcre-devel -y # PCRE Library
yum install zlib zlib-devel -y # zlib Library
編譯安裝:
# 進入解壓後的目錄中 編譯安裝 [指定使用者/組] [--with-追加自帶模組名稱]
./configure --prefix=/usr/local/nginx [--user=www --group=www] [--with-http_gzip_static_module]
make && make install
進入主程序目錄:/usr/local/nginx/bin
nginx # 啟動
nginx -s stop # 停止,立即
nginx -s quit # 退出,處理完現有任務後
nginx -s reopen # 重啟
nginx -s reload # 過載設定,交替更新工作程序
docker 執行 nginx 很簡單:
拉取映象:docker pull nginx
啟動容器:docker run -d --name=ngx-a -p 80:80 nginx
瀏覽器開啟主機IP顯示 NGINX 歡迎頁面。
影響 Nginx 的系統關聯項
Firewall/UFW 防火牆:埠的開放
SELinux 許可權的限制:請求後端的許可權
nginx 的組態檔預設存於 /etc/nginx/nginx.conf,其中通過 include 引入其它目錄子組態檔。
設定模板範例
###### 全域性塊
worker_processes auto; # 工作程序數
error_log /var/log/nginx/error.log notice; # 錯誤級別記錄
events {
worker_connections 1024; # 單個工作程序,可承載的最大連線數
}
http {
###### MIME 設定
include /etc/nginx/mime.types; # 副檔名與檔案型別對映表
default_type application/octet-stream; # mime.types 不包含時的預設設定
###### 請求紀錄檔設定
log_format log-format-a '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log log-format-a; # 存取紀錄檔路徑 及參照格式
###### 後端可用服務設定
upstream backend-server-name {
server 192.168.1.101:80;
server 192.168.1.102:80;
}
server {
###### 請求匹配
listen 80; # 使用者端請求的埠
server_name _; # 使用者端請求的域名;區分不同服務(可多個空格區分,支援正則)
location / {
###### 重寫設定
rewrite ^<規則>$ <目的地> break;
###### 轉發到後端設定
proxy_pass http://backend-server-name$request_uri; # 轉發到後臺服務地址,來自於 upstream 項
proxy_http_version 1.1; # 指明版本(1.1預設為keep-alive長連線,1.0預設為短連線)
proxy_set_header Host $host; # 保持原來的請求域名
proxy_ignore_client_abort on; # 使用者端斷網時,是否中斷對後端的請求
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 從遠端使用者端IP到伺服器端的層層代理轉發IP,多IP追加空格分隔
###### Cookie 域名/路徑
proxy_cookie_domain {backend-domain} {request-domain};
proxy_cookie_path {backend-path} {request-path};
}
###### 指定檔案拒絕所有存取
location ~ ^/(\.user.ini|\.ht|\.git|\.svn|\.project|LICENSE|README.md){
deny all;
}
###### 限制的使用者端
location \ {
deny 172.18.0.101; # 拒絕的ip
allow 172.18.1.10; # 允許的ip
}
###### 使用者端快取設定
location ~* \.(js|css|jpg|svg|gif|png)$ {
if (-f $request_filename) { # -f:只能是檔案,因為這用-f判斷了
expires 30d; # 快取有效時長 30 天
break;
}
}
###### 防盜鏈設定
location ~* \.(gif|jpg|png|bmp)$ { # 指定格式禁止的請求來源:google/baidu
valid_referers none blocked *.ttlsa.com server_names ~\.google\. ~\.baidu\.;
if ($invalid_referer) {
return 403; # 狀態碼
#rewrite ^/ http://www.ttlsa.com/403.jpg;
}
}
###### (前端) 錯誤機制
error_page 404 /404.html; # 錯誤碼 轉向的 錯誤頁
error_page 500 502 503 504 /50x.html; # 錯誤碼 轉向的 錯誤頁
location = /50x.html { # 錯誤頁 指向的 靜態頁面
root /usr/share/nginx/html;
}
}
}
把前端站點部署到Nginx:
server {
listen 80;
server_name xxx.com;
# 前端設定
location / {
# 前端站點路徑
root /home/vue/dist;
index index.html
}
# 後端設定
location /api {
proxy_set_header host $HOST;
proxy_pass http://192.168.1.101:8081;
}
}
在設定項 upstream 中,負責提供可用的服務地址列表,並可指定負載均衡的實現方式。
輪詢 Round-Robin:將存取按序依次請求到後端各個伺服器上,能確保平均負載
upstream backend-a {
server 192.168.1.101:80;
server 192.168.1.102:80;
}
權重 Weight:按百分比請求到後端伺服器上,常用到硬體設定不同的場景
upstream backend-b {
server 192.168.1.101:80 weight=3;
server 192.168.1.102:80 weight=7;
}
最少連線 Least-Connect:處理請求少的後端服務優先接收新請求
upstream backend-c {
least_conn;
server 192.168.1.101:80;
server 192.168.1.102:80;
}
IP-Hash:相同的存取IP落到後端同個伺服器上,所以支援對談保持,但不是絕對平均負載
upstream backend-d {
ip_hash;
server 192.168.1.101:80;
server 192.168.1.102:80;
}
第三方的對談保持 sticky_cookie_insert,同時支援負載均衡。
限流:通過對並行/請求進行限速來保護系統,防止系統過載癱瘓而不能提供服務;為了更好控制整個系統的負載情況,即使阻止了某些請求,系統繼續提供服務。
http_limit_conn:單個IP同時允許的連線限制
http {
# 連線限流定義
# - $binary_remote_addr:限制物件(使用者端)
# - zone:限制自定義名稱
# - 10:記憶體中用10兆空間儲存連線記錄
limit_conn_zone $binary_remote_addr zone={limits-name}:10m;
server {
location /search/ {
# 單個IP同時允許建立多少連線(並行限制)
limit_conn {limits-name} 1;
}
}
}
http_limit_req:單個IP請求頻率的限制;次/每秒;
http {
# 請求限流定義
# - $binary_remote_addr:限制物件(使用者端)
# - zone:定義限制(策略)名稱
# - 10m:用十兆空間記錄存取次數
# - rate:每秒10次的請求處理速率
limit_req_zone $binary_remote_addr zone={limits-name}:10m rate=1r/s;
# 請求限流定義
# - $server_name:限制物件,對指定伺服器請求的限制
limit_req_zone $server_name zone={limits-name}:10m rate=10r/s;
server {
location /search/ {
# 參照以上定義的限流策略,做以下設定(漏桶方式)
# - burst:最多接收5個排隊使用者IP,處於等待處理狀態(容量)
# - nodelay:超出排隊之外的更多請求,拒絕並返回503(溢位)
limit_req zone={limits-name} [burst=5] [nodelay];
}
}
}
http_limit_rate:向用戶端傳輸響應的速率限制;位元組/每秒/每連線;0不限制
http {
server {
location /download/ {
# 頻寬限制
limit_rate_after 5m; # 初始限速5m
limit_rate 500k; # 超出後限速500k
}
}
}
熔斷:當後端服務發生指定頻率錯誤後,Nginx觸發熔斷措施,不再請求此後端服務,直接返回預設內容到使用者端。
upstream http_backend {
# 10s內出現3次錯誤,該伺服器將被視為不可用(熔斷)
server 192.168.1.101:8080 max_fails=3 fail_timeout=10s;
server 192.168.1.102:8080 max_fails=3 fail_timeout=10s;
}
當然也有容錯機制,Nginx 預設自動轉向其它服務再請求,相關設定:proxy_next_upstream
# 工作程序數
worker_processes auto; # 建議 CPU核心數|CPU執行緒數
# 最大支援的連線(open-file)數量;最大值受限於 Linux open files (ulimit -n)
# 建議公式:worker_rlimit_nofile > worker_processes * worker_connections
worker_rlimit_nofile 65535;
events {
use epoll; # 高效的IO多路複用(RedHat6+ 都支援 epoll)
multi_accept on; # 設定一個程序是否同時接受多個網路連線
worker_connections 10240; # 單個工作程序,可承載的最大連線數;
}
http {
###### 零拷貝技術
sendfile on; # 開啟不讀到(應用本身)記憶體,直接通過系統發出資料
#sendfile_max_chunk 2m; # 每個程序每次呼叫傳輸數量不能大於設定的值,預設0為無上限。
###### 網路傳輸
# on:累積到一定容量的資料再發,傳送次數少
# off:有資料就發,傳送次數多,佔用網路資源
tcp_nopush on;
###### 長連線;使用者端比較分散,keepalive 預設值已經足夠,個人不建議重設
###### 響應資料 開啟壓縮模式
gzip on;
gzip_vary on; # 為相容老瀏覽器,追加到Header的壓縮標註
gzip_proxied any; # 所有代理請求都壓縮,也可指定型別
gzip_comp_level 2; # 壓縮等級1-9(比例)等級越大 壓縮越高 越耗CPU
gzip_min_length 128; # 壓縮前提,當返回內容大於指定時再壓縮
gzip_types text/css text/xml image/svg+xml; # 指定壓縮的檔案型別;更多壓縮項可參考 mime.types
}
http {
upstream backend_A {
# ...
# 長連線;所有請求匯聚到後端伺服器,並行時是有必要在此基礎上重配 keepalive 的
# 以下 keepalive 需要 http 1.1 版本,並且 header connection close
keepalive 100; # 每個Worker與後端的連線池中,保持指定量的空閒連線數(QPS的10%)
keepalive_time 1h; # 每個長連線的(忙碌+空閒)總時長,超出後強制失效
keepalive_timeout 60s; # 每個長連線,最大空閒時長,超出後自動關閉長連線
keepalive_requests 1000; # 每次長連線支援的 Request 次數,超出後自動關閉
}
server {
location \ {
proxy_pass http://backend_A;
proxy_http_version 1.1; # 1.1 支援 keep-live
proxy_set_header connection ""; # 覆蓋使用者端的連線頭
}
}
}
多級快取:通過 Nginx 實現各種快取的設定,瀏覽器快取、CDN快取、Nginx記憶體、代理快取、後端服務應用快取等。
資源靜態化 ssi 模組:請求結果生成靜態頁,Nginx設定攔截,更多的請求直接讀靜態頁後返回;減少後端請求,定時生成靜態頁。
靜態資源同步 rsync:每臺服務都安裝,監控目錄變化,把生成的靜態頁推播到多臺伺服器。
合併請求 concat 第三方模組:將多個靜態資原始檔 合併為一次請求載入完成,減少並行量;淘寶範例:??xxx.js,yyy.js,zzz.js
自動更新 upstream 上的可用服務地址,Nginx 的商業版才提供,開源的可選第三方模組;這裡後端叢集管理用的是 Consul;所以這裡使用 Consul 提供的配套工具 Consul-Template。
當後端叢集 consul 上的應用服務有變動時,工具 consul-template 負責拉取 consul 最新的健康應用服務列表,並生成 nginx conf 後再過載 Nginx。
1、下載部署 consul-template
# 從官網 https://releases.hashicorp.com/consul-template/ 下載軟體包
curl -O https://releases.hashicorp.com/consul-template/0.31.0/consul-template_0.31.0_linux_amd64.zip
unzip consul-template_0.31.0_linux_amd64.zip consul-template # unzip解壓軟體包並重新命名
mv consul-template /usr/local/bin/ # 移動到特定目錄
/usr/local/bin/consul-template -v # 驗證安裝效果,顯示版本號
2、建立生成 nginx conf 的模板檔案 ngx-server-conf.tmp,用於生成指定格式的 nginx.conf 檔案;內容範例如下:
為便於集中管理組態檔,存放於 nginx 預設的設定目錄下 /etc/nginx/conf.d/ngx-server-conf.tmp
########## 自動生成多個 upstream
{{range services}} {{$name := .Name}} {{$service := service .Name}}
upstream {{$name}} {
least_conn;
{{range $service}}server {{.Address}}:{{.Port}};
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
} {{end}}
########## upstream end ##########
server {
listen 80;
server_name xxx.com;
location / {
root /usr/share/nginx/html/;
index index.html;
}
########## 自動生成多個 locattion
{{range services}} {{$name := .Name}}
location /api/{{$name}} {
proxy_pass http://{{$name}};
proxy_http_version 1.1;
}
{{end}} ########## location end ##########
}
更多的模板檔案語法可參考官網說明:consul-template templating language
3、建立 consul-template 的組態檔 ctmp.hcl,用以設定執行引數。內容範例如下:
為便於集中管理組態檔,存放於 nginx 容器內的預設設定目錄下 /etc/nginx/conf.d/ctmp.hcl
# 連線 consul 的設定
consul {
address = "172.18.0.3:8500" # consul node
}
# 生成 nginx config file 的設定
template {
source = "/etc/nginx/conf.d/ngx-server-conf.tmp" # consul-template 的設定模板檔案
destination = "/etc/nginx/conf.d/default.conf" # 生成的 nginx-server 組態檔
command = ["nginx", "-s", "reload"] # 執行的命令,以過載 Nginx 設定
}
# 總結步驟:從 address 拉資料,通過格式 source 生成 destination 設定,最後 command 過載Nginx。
以上更多的設定項參考官網說明:consul-template configuration options
4、啟動執行 consul-template
# nginx 容器中啟動 consul-template
/usr/local/bin/consul-template -config /etc/nginx/conf.d/ctmp.hcl
# 驗證效果:可檢視生成的 nginx conf
cat /etc/nginx/conf.d/default.conf
效果:先檢視當前 nginx default.conf;之後停掉後端一個應用服務,對比 nginx 前後兩個 default.conf 的內容變化。
高可用 - 雙機熱備:主機和從機通過TCP/IP網路連線,正常情況下主機處於工作狀態,從機處於監視狀態,一旦從機發現主機異常,從機將會在很短的時間之內代替主機,完全實現主機的功能。
工具 Keepalived,安裝到內網中每個裝有 Nginx 的系統上:
多個 Keepalived 範例形成一個組,組成員有 Master/slave 之分,時時監控組內所有 Keepalived 範例的執行情況;
Keepalived 通過一個虛擬IP加入到 Master 網路卡上,所以通過虛擬IP能夠直接連線到 Master上,也就是其中一個 Nginx;
一個 Keepalived 成員宕機後,從 Slave 中選舉出新的 Master,也就是把虛擬IP自動加入到新的 Master上,持續提供服務;
對外僅通過內網的虛擬IP完成與 Nginx 的連線,而不關心使用的 Nginx 在哪臺機上。
安裝 Keepalived:yum install -y keepalived
組態檔:/etc/keepalived/keepalived.conf
設定模板:可僅留 global_defs,vrrp_instance,virtual_ipaddress
global_defs {
router_id <key> # 同組不重複的唯一標識
}
vrrp_instance <Instance-Name> {
state MASTER # MASTER/BACKUP;有主優先執行
interface <net-card-name> # 指定虛擬IP寄存的網路卡
priority 100 # 相同角色的優先順序,越大越優先
advert_int 1 # 間隔秒檢測一次成員的執行狀況
authentication { # 成員間的通訊憑證
auth_type PASS # 同組相同的方式
auth_pass 1111 # 同組相同的編碼,保持成員互通
}
virtual_ipaddress { # 追加新IP,對外提供的通訊入口
192.168.17.200 # 同網段的、未被使用的、虛擬新IP
}
}
啟動後,可在 Master 中的指定網路卡中看到已追加的虛擬IP;不妨 ping 下你的虛擬IP...
再設定一臺 Keepalived,注意這裡設定的不同項為:router_id / state / priority
瀏覽器中存取虛擬IP,就直接存取了 Master 上的 Nginx;
關掉Master伺服器後,Keepalived 將虛擬IP又新增到了備用伺服器上了;
虛擬IP繼續提供正常的 Nginx 服務,瀏覽器正常存取虛擬IP地址;
當 Master 修復啟動後,Keepalived 又將虛擬IP自動切換到 Master 上,始終以 Master 優先使用。
注意
Keepalived 僅檢測自己主程序的執行狀況,並不是檢測 Nginx 的執行狀況;
所以:當 Nginx 錯誤,而 Keepalived 執行正常時,並不能達到 Master 轉移的效果;
方案:用指令碼定時檢測 Nginx 的主程序,Nginx發生錯誤時主動 Kill Keepalived,達到 Master 轉移的效果。
建立檢測指令碼檔案 /etc/keepalived/check_nginx.sh 內容範例:
#!/bin/bash
# 檢測 Nginx worker process 執行的個數
ckn=$(ps -C nginx --no-heading | wc -l)
# 當沒有 Nginx worker process 執行時
# 製造keepalived異常,使得啟用備用伺服器
if [ $ckn -eq 0 ]; then
kill $(pidof keepalived)
fi
並賦予使用者可執行許可權;如:chmod +x /etc/keepalived/check_nginx.sh
Keepalived 指令碼檢測設定內容範例:
# global_defs ...
vrrp_script check_nginx_status { ### 定義檢測策略
user root # 負責檢測的使用者
interval 1 # 檢測間隔秒
script /etc/keepalived/check_nginx.sh # 檢測的可執行檔案
}
vrrp_instance <Instance-Name> {
# ...
track_script {
check_nginx_status # 參照檢測策略
}
# ...
}
在 Master 上停止 Nginx 執行 試試看...