寫在前面的話
在我們日常的工作中,不可能所有的服務都是簡單的 HTML 靜態網頁,nginx 作為輕量級的 WEB 服務器,其實我們將它用於更多的地方還是作為我們網站的入口。不管你是后端接口,還是前端頁面,我們讓用戶的請求都到這個服務。原因大致有以下幾個:
1. 集中管理更便於管理。
2. 對外服務都是需要公網 IP 的,需要帶寬,如果每台機器都專門配置公網 IP 和帶寬,實在是太浪費,可以看看最近我整理的幾個雲服務商帶寬收費情況:
帶寬越高,費用越高,這種高還不是成幾何式增長。
3. 我們的服務一般不會是單點服務,那么前后端如果需要通信,怎么配置地址,每個都配置?會不會太麻煩了點。
4. 在我們生成服務器中,類似 redis / 數據庫這類的服務器一般是不允許聯網的為了安全,但是我們是不是每次都必須要登錄上服務器去查數據呢?
這還是一些簡單的理由,接下來我們來聊聊幾種代理,順便說說他們的應用場景,當然這種設計對於中小型公司是足夠了,畢竟沒去過大廠,不知道他們的具體架構和實現方式。
TCP 代理
在說 TCP 代理之前我們肯定還是要先了解下代理是什么東西?
代理(proxy)其實就是一個網絡信息的中轉站,我們使用一個圖來表示:
比如這樣一個場景,用戶在外邊,能夠通過公網能夠訪問到雲服務商集群中的一台具有公網 IP 的服務器,但是集群內部其它服務器都是不具備公網 IP 的,所以通過公網,我們無法訪問到,但是有公網 IP 的服務器恰好在集群中,它的內網網卡和這些沒有公網 IP 的機器是可以通信的,這意味着這台機器既可以和用戶通信,又可以和后端的集群通信。那我們就得想辦法,用戶要訪問后面的集群,讓這個有公網 IP 的服務器幫忙傳達一下,那么這個,當個中間人,實現用戶和后端集群之間互相傳話,於是這個有公網 IP 機器就成為了代理服務器。
那啥又是 TCP 代理?
在我們日常訪問 WEB 應用,我們都是使用 http://,https://。但是有些服務不是 http 的,比如連接 MySQL 這種,這種明顯不是 WEB 服務,所以我們不能像代理 WEB 服務一樣代理它。而這類服務,就是 TCP 服務,我們得專門使用 TCP 代理。
我們同時舉例 TCP 代理 MySQL 來具體說說如何使用配置。
在我們編譯得時候加入了 --with-stream 參數,該參數是我們使用代理不可或缺的。
同樣,我們在主配置文件 nginx.conf 中通過 include 來配置單獨的目錄,用於放置 TCP 配置的配置文件:
值得注意是,TCP 代理不是 HTTP 服務,所有我們的 include 和之前的位置不一樣,我們得放在 http 的外層:
user root; ... http { ... } stream { include tcp/*.conf; }
我們新建 tcp 目錄:我這里為了方便使用直接 nginx 使用 root 用戶
mkdir /data/services/nginx/conf/tcp
在目錄下增加 MySQL 代理配置文件:mysql-proxy-demo.conf
upstream MYSQL-PROXY-DEMO { hash $remote_addr consistent; server 192.168.10.204:3306; } server { listen 5000; proxy_connect_timeout 10s; proxy_timeout 300s; proxy_pass MYSQL-PROXY-DEMO; }
簡單說明:
1. 在 nginx 中,如果需要對后端多個機器做代理,就需要使用到 upstream,MYSQL-PROXY-DEMO 是給這個 upstream 取的名稱,要求唯一。
2. hash xxx 是一種調度模式,當然這里只寫了一條 server 記錄,所有不存在調度到其他節點問題。
3. server xxx 是一條需要代理的記錄,每一條一個 server。
4. server 段和 http WEB 服務類似,但是不需要 server_name。
5. proxy_connect_timeout 為連接超時時間,proxy_timeout 代理超時時間。
6. proxy_pass,代理中最為關鍵的一句,指名了我們把這個端口代理到哪個服務或者哪個 upstream。后面還會用到。
重載 nginx 此時我們測試數據庫連接:
可以看到,我們成功的使用代理服務器的 IP + 端口通過 Navict 連接到了其它主機的 MySQL 數據庫。
正向代理
正向代理不是我們使用的重點,因為在日常的使用中用的並不多,但是在某些特殊的場景下很有用。
比如有個局域網用戶,無法訪問互聯網,但是局域網中另外一台機器卻能夠訪問到互聯網,所有我們可以通過那台機器作為代理去訪問互聯網。
正向代理的好處在於能夠對需要訪問的網站隱藏用戶的真實信息。
這是系統為我提供的解決方案,但是並不是很好用,如果你想直接使用,請直接跳到后面第三方模塊搭建正向代理。
我們在 vhosts 目錄下新建配置:forward-proxy-demo.conf
server { resolver 8.8.8.8; access_log off; listen 6080; location / { proxy_pass $scheme://$http_host$request_uri; proxy_set_header HOST $http_host; # 配置緩存大小,關閉磁盤緩存讀寫減少I/O,以及代理連接超時時間 proxy_buffers 256 4k; proxy_max_temp_file_size 0; proxy_connect_timeout 30; # 配置代理服務器 Http 狀態緩存時間 proxy_cache_valid 200 302 10m; proxy_cache_valid 301 1h; proxy_cache_valid any 1m; proxy_next_upstream error timeout invalid_header http_502; } }
簡單說明:
紅色部分為核心配置,這里我們直到了,在 nginx 中我們是可以通過 proxy_set_header 來處理請求頭的。
我們在另外一台不能上網的機器上增加配置:
重載 nginx 后我們在不能上網的機器上執行 curl 百度:
curl -I --proxy 192.168.100.111:6080 http://www.baidu.com
結果如下:
當然我們也可以將代理配置定義成環境變量:
export http_proxy=http://192.168.100.111:6080
這樣就能直接執行:
第三方模塊搭建正向代理
當然,上面的配置都是針對 HTTP 的,對於 HTTPS 代理或者說整個正向代理,我們推薦使用第三方模塊:ngx_http_proxy_connect_module
GITHUB 地址:
將下載的 zip 包上傳到服務器,重新編譯 nginx,具體方法參考前面的動態添加模塊:
1. 安裝依賴:
yum install -y patch
2. 解壓打補丁,編譯:
從 GITHUB 上面,我們可以看到各個版本的 nginx 對應的補丁版本:
cd /data/packages/nginx unzip ngx_http_proxy_connect_module-master.zip cd nginx-1.16.0/ patch -p1 < /data/packages/nginx/ngx_http_proxy_connect_module-master/patch/proxy_connect_rewrite_101504.patch
編譯不安裝:
./configure --prefix=/data/services/nginx \ --user=nginx \ --group=nginx \--with-http_stub_status_module \ --with-http_gzip_static_module \ --with-http_secure_link_module \ --with-http_flv_module \ --with-http_ssl_module \ --with-http_mp4_module \ --with-stream \ --with-http_realip_module \ --with-http_v2_module \ --with-http_sub_module \ --with-http_image_filter_module \ --with-pcre=/data/packages/nginx/pcre-8.43 \ --with-openssl=/data/packages/nginx/openssl-1.1.1c \ --with-zlib=/data/packages/nginx/zlib-1.2.11 \ --add-module=/data/packages/nginx/nginx-upload-module-master \ --add-module=/data/packages/nginx/nginx-upstream-fair-master \ --add-module=/data/packages/nginx/ngx_cache_purge-master \ --add-module=/data/packages/nginx/ngx-fancyindex-master \ --add-module=/data/packages/nginx/echo-nginx-module-master \ --add-module=/data/packages/nginx/ngx_http_proxy_connect_module-master # 編譯 make
3. 備份替換舊版:
# 備份 mv /data/services/nginx/sbin/nginx /data/backup/nginx/nginx_$(date +%F) # 更新 cp /data/packages/nginx/nginx-1.16.0/objs/nginx /data/services/nginx/sbin/ # 查看 /data/services/nginx/sbin/nginx -V
如圖:
4. 添加 nginx 正向代理配置:
server { listen 6080; resolver 202.96.128.166; resolver_timeout 30s; # 代理配置 proxy_connect; proxy_connect_allow 443 563; proxy_connect_connect_timeout 10s; proxy_connect_read_timeout 10s; proxy_connect_send_timeout 10s; location / { proxy_pass http://$host; proxy_set_header Host $host; } }
重載配置訪問測試:
curl -I --proxy 192.168.100.111:6080 http://www.baidu.com
curl -I --proxy 192.168.100.111:6080 https://www.alipay.com
HTTP 訪問結果:
HTTPS 訪問結果:
當然,我們也可以設置環境變量:
export http_proxy=http://192.168.100.111:6080 export https_proxy=http://192.168.100.111:6080 no_proxy="localhost,127.0.0.1,localaddress,.localdomain.com"
最后,小結一下:正向代理配置雖然能夠滿足我們的一定需求,但是有些時候不是很穩定,包括在配置過程中,有時候並不能一次就能訪問成功,需要多測試幾次。
反向代理 / 負載均衡
反向代理一直是我們 nginx 服務配置的重中之重,我們工作的項目中大部分其實都是圍繞着反向代理展開的。如果你用 nginx,你說你沒有配置過靜態資源 WEB 我相信,但是你沒有用過反向代理,那你一定不是做運維的。
那什么是反向代理?
這需要我們和正向代理結合起來理解,我們之前正向代理的時候是我們代理別人的服務讓我們能夠訪問到。
那么反向代理就是代理我們的服務讓別人能夠訪問到,是不是一下子就清晰了。
我們本次測試環境用到了三台機器,一台是我們的 nginx,另外兩台是安裝了 tomcat 服務的服務器。我們要實現以下圖示:
用戶訪問 nginx 的 8090 端口調度到后端的 TOMCAT 8080 上面去。
至於 TOMCAT 怎么安裝部署這里就不做過多說明,這里做了個小處理,在 TOMCAT webapps 下面默認 ROOT 項目的 index.jsp 文件增加了本機 IP 用於區分:
此時我們啟動兩個 TOMCAT 訪問測試:
節點1結果:
節點2結果:
在 nginx 的 vhosts 目錄下增加如下配置:reverse-proxy-demo.conf
upstream REVERSE-PROXY-DEMO { ip_hash; server 192.168.100.112:8080 weight=1 max_fails=3 fail_timeout=10s; server 192.168.100.113:8080 weight=1 max_fails=3 fail_timeout=10s; } server { listen 8090; server_name localhost; location / { proxy_redirect off; 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_connect_timeout 30; proxy_send_timeout 30; proxy_read_timeout 30; proxy_pass http://REVERSE-PROXY-DEMO; } }
如 TCP 代理一般,TCP 代理其實也是反向代理的一種,我們定義的 upstream 的名稱要求唯一。
重載 nginx 訪問測試:
可以看到請求被分配到了 100.113 這台機器上面去了!
我們之前就在使用 upstream,但是 upstream 到底是啥我們一直沒說,其實 upstream 就是負載均衡。
從字面上的意思就可以理解,負載均衡就是均衡的,按照特定的調度算法,將請求調度到指定的節點(upstream server)。
upstream 配置說明:
1. nginx 負載均衡調度算法加上我們安裝的 fair 模塊,大致有以下 4 種:
調度算法 | 說明 |
---|---|
權重輪詢(默認) | 按照順序逐一分配到不同的后端。自動剔除失敗的機器,使訪問不受影響。 |
ip_hash | 每個請求按照 IP 的 Hash 結果分配,使來自同一 IP 的固定訪問到同一后端。能解決部分程序 session 沒共享的問題 |
fair | 更智能的算法,可以根據頁面大小和加載速度進行智能負載,響應快的優先分配。 |
url_hash | 需要按照 nginx hash 模塊,按照訪問的 URL 定向到某個機器上,能提升后端緩存服務器的效率。 |
日常用到比較多的就是前三個。
2. server 后面的參數:
參數 | 說明 |
---|---|
weight | 分配到請求權重,權重比例多高分配到請求的機會越大。 |
max_fails | 最大的失敗連接次數。 |
fail_timeout | 等待請求的目標服務器響應的時長。 |
backup | 當所有機器都 down 掉才會調度到這台機器。 |
down | 手動停用某台機器。 |
這其實就是一些健康檢查參數,但是這些參數存在不足,在實際應用中,可以結合 keepalived 來完成,后面會單獨說明。
server 段關於反向代理的一些配置:
參數 | 說明 |
---|---|
proxy_redirect | 重寫應答頭部的報文 |
proxy_connect_timeout | nginx 將一個請求發送至 upstream server 之前等待的最大時長 |
proxy_set_header | 將發送至 upsream server 的報文的某首部進行重寫 |
proxy_cookie_domain | 將 upstream server 通過 Set-Cookie 首部設定的 domain 修改為指定的值,可以為字符串、正則或變量 |
proxy_cookie_path | 將 upstream server 通過 Set-Cookie 首部設定的 path 修改為指定的值,可以為字符串、正則或變量 |
proxy_hide_header | 設定發送給客戶端的報文中需要隱藏的首部 |
proxy_send_timeout | 發送至 upstream server 的寫操作的超時時長 |
proxy_read_timeout | 發送至 upstream server 的讀操作的超時時長 |
proxy_pass | 指定將請求代理至 upstream server 的 URL 路徑 |
其實上面的配置我們可以簡單的做個調整就能變成我們反向代理的配置模板。
小結
簡單的 TCP / 正向 / 反向代理負載均衡就這些內容,當然還要優化的空間,后面會專門針對 nginx 配置優化再度進行說明。