返回文章列表

Nginx 與 HAProxy 代理服務比較與實戰

本文深入比較 Nginx 與 HAProxy 兩大代理服務,探討它們的優缺點、適用場景以及如何在微服務架構中利用 Consul 和 Consul Template 實作自動化組態和負載平衡。文章涵蓋 Nginx 的基本組態、反向代理設定、HAProxy

Web 開發 系統設計

在微服務架構中,有效管理服務間的通訊和流量至關重要。Nginx 和 HAProxy 作為常用的代理伺服器,都提供了負載平衡、反向代理等功能。本文將比較 Nginx 和 HAProxy 的特性,並探討如何使用 Consul 和 Consul Template 實作動態組態和服務發現。Nginx 以其事件驅動架構聞名,能夠高效處理大量併發連線,適用於靜態內容服務和反向代理。然而,在需要更進階的負載平衡策略或零停機佈署時,HAProxy 則更具優勢。HAProxy 提供了更豐富的健康檢查機制和 Session 持久化功能,並支援更靈活的組態方式。透過 Consul 和 Consul Template,可以根據服務註冊資訊動態更新代理伺服器的組態,實作自動化服務發現和負載平衡。

代理服務的抉擇:nginx 與 Apache 的比較

在眾多的代理服務中,nginx 和 Apache 是兩個最受矚目的選擇。本文將探討這兩者的優缺點,以幫助讀者瞭解哪一個更適合特定的需求。

nginx 簡介

nginx(發音為"engine x")是一款由 Igor Sysoev 建立的 HTTP 和反向代理伺服器,同時也支援郵件代理和 TCP 代理服務。nginx 最初用於支援多個俄羅斯網站,後來逐漸成為世界上最繁忙網站的首選伺服器之一,包括 NetFlix、Wordpress 和 FastMail 等。根據 Netcraft 的統計,nginx 在 2015 年 9 月時已經佔據了全球最繁忙網站的 23% 份額,使其成為僅次於 Apache 的第二大伺服器。

Apache 簡介

Apache 是另一個歷史悠久且廣泛使用的伺服器軟體。它擁有龐大的使用者群,部分原因是因為 Tomcat(一種流行的應用程式伺服器)執行在 Apache 之上。Apache 的模組化設計使其能夠處理幾乎任何程式語言。

nginx 與 Apache 的比較

雖然 Apache 很流行,但它的設計缺陷使其在面對大量請求時表現不佳。Apache 會為每個請求產生新的程式和執行緒,這會消耗大量記憶體和 CPU 資源。當達到可組態的程式限制時,Apache 會拒絕新的連線請求。相比之下,nginx 則採用非同步、非阻塞的事件驅動架構,使其能夠處理更多的並發請求,同時保持較低的資源佔用。

nginx 的缺點是它主要設計用於提供靜態內容。如果需要提供由 Java、PHP 等動態語言生成的內容,Apache 可能是更好的選擇。然而,在本文的案例中,nginx 的缺點並不重要,因為我們需要的是具有負載平衡功能的代理服務,而不是直接提供靜態或動態內容。

使用 Ansible 設定 nginx

在設定 nginx 代理服務之前,讓我們先看看將要執行的 Ansible 檔案。nginx.yml 劇本與之前使用的類別似,我們將執行之前已經執行過的角色,並新增 nginx 角色。

- hosts: proxy
  remote_user: vagrant
  serial: 1
  sudo: yes
  roles:
    - common
    - docker
    - docker-compose
    - consul
    - registrator
    - consul-template
    - nginx

roles/nginx/tasks/main.yml 角色也沒有什麼特別之處。

- name: Directories are present
  file:
    dest: "{{ item }}"
    state: directory
  with_items: directories
  tags: [nginx]

- name: Container is running
  docker:
    image: nginx
    name: nginx
    state: running
    ports: "{{ ports }}"
    volumes: "{{ volumes }}"
  tags: [nginx]

- name: Files are present
  copy:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
  with_items: files
  register: result
  tags: [nginx]

- name: Container is reloaded
  shell: docker kill -s HUP nginx
  when: result|changed
  tags: [nginx]

- name: Info is sent to Consul
  uri:
    url: http://localhost:8500/v1/kv/proxy/ip
    method: PUT
    body: "{{ ip }}"
  ignore_errors: yes
  tags: [nginx]

程式碼解密:

  1. 建立目錄:使用 file 模組確保指定的目錄存在。
  2. 執行 nginx 容器:使用 docker 模組確保 nginx 容器正在執行,並對映必要的埠和卷。
  3. 複製檔案:使用 copy 模組將必要的檔案複製到目標主機。
  4. 重新載入 nginx:如果檔案有變更,則重新載入 nginx 容器。
  5. 將資訊傳送到 Consul:使用 uri 模組將 nginx 的 IP 位址傳送到 Consul。

nginx 設定檔

roles/nginx/files/services.conf 是 nginx 的設定檔。

log_format upstreamlog
'$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" "$gzip_ratio" '
'$upstream_addr';

server {
    listen 80;
    server_name _;

    access_log /var/log/nginx/access.log upstreamlog;

    include includes/*.conf;
}

程式碼解密:

  1. 定義日誌格式:使用 log_format 定義名為 upstreamlog 的日誌格式。
  2. 設定伺服器:監聽 80 埠,並設定伺服器名稱為 _,表示匹配所有主機名稱。
  3. 設定存取日誌:將存取日誌寫入 /var/log/nginx/access.log,並使用 upstreamlog 日誌格式。
  4. 包含其他設定檔:使用 include 陳述式包含 includes 目錄下的所有 .conf 檔案。

使用Nginx作為反向代理服務

在現代的微服務架構中,反向代理服務扮演著至關重要的角色。它不僅能夠幫助我們管理多個服務的存取,還能提升系統的可擴充套件性和安全性。本文將探討如何使用Nginx作為反向代理服務,並自動化其組態過程。

Nginx的基本組態

首先,我們來看看Nginx的基本組態。以下是一個簡單的Nginx組態範例:

http {
    include upstreams/*.conf;
    server {
        listen 80;
        server_name _;
        include sites-enabled/*.conf;
    }
}

內容解密:

  • http區塊定義了Nginx處理HTTP請求的主要組態。
  • include upstreams/*.conf;用於引入上游伺服器的組態,這些組態通常定義了後端服務的相關資訊。
  • server區塊定義了一個虛擬伺服器,它監聽80埠並接受所有發往該埠的請求。
  • server_name _;表示該伺服器區塊將處理所有未被其他伺服器區塊匹配的請求。
  • include sites-enabled/*.conf;引入了具體的網站或服務組態。

沒有反向代理的困境

在沒有反向代理服務的情況下,我們直接透過Docker Compose執行應用程式:

wget https://raw.githubusercontent.com/vfarcic/books-ms/master/docker-compose.yml
export DOCKER_HOST=tcp://proxy:2375
docker-compose up -d app
docker-compose ps
curl http://proxy/api/v1/books

輸出結果顯示404錯誤,因為我們的服務執行在隨機埠上,而客戶端無法直接存取。

內容解密:

  • wget下載Docker Compose組態檔案。
  • export DOCKER_HOST指定了Docker守護程式的位置。
  • docker-compose up -d app在後台啟動應用程式。
  • curl命令嘗試存取服務,但由於埠不匹配而失敗。

手動組態Nginx

為瞭解決上述問題,我們需要手動組態Nginx。首先,我們取得服務的實際埠:

PORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort}}' vagrant_app_1)
echo $PORT
curl http://proxy:$PORT/api/v1/books | jq '.'

然後,建立Nginx組態檔案並重新載入Nginx:

echo "location /api/v1/books { proxy_pass http://10.100.193.200:$PORT/api/v1/books; }" | tee books-ms.conf
scp books-ms.conf proxy:/data/nginx/includes/books-ms.conf
docker kill -s HUP nginx

內容解密:

  • 取得容器對映的埠號並儲存到PORT變數中。
  • 使用curl命令測試服務是否可存取。
  • 建立Nginx組態檔案,將請求代理到正確的服務位址和埠。
  • 使用scp命令將組態檔案傳輸到Nginx伺服器。
  • 重新載入Nginx組態。

自動化Nginx組態

為了實作自動化,我們利用Consul Template工具。該工具能夠根據Consul中的服務註冊資訊動態生成Nginx組態檔案。

首先,我們擴充套件服務到兩個例項:

docker-compose scale app=2
docker-compose ps

內容解密:

  • docker-compose scale命令擴充套件應用程式到兩個例項。
  • docker-compose ps命令檢查當前執行的容器。

服務與反向代理關係圖

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 服務與反向代理關係圖

rectangle "請求" as node1
rectangle "轉發請求" as node2
rectangle "註冊" as node3
rectangle "提供服務資訊" as node4
rectangle "生成組態" as node5

node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5

@enduml

此圖示展示了客戶端請求如何透過Nginx反向代理轉發到後端的服務例項,以及Consul和Consul Template如何協同工作來自動化Nginx的組態過程。

使用 Consul Template 自動化 Nginx Proxy 與負載平衡

在微服務架構中,當服務有多個例項執行時,傳統的 proxy 設定方式將不再適用。因為每個服務例項可能會使用不同的隨機埠,這使得我們無法直接指定固定的 IP 和埠進行代理轉發。在這種情況下,我們需要結合 proxy 與負載平衡(load balancing)技術來實作請求的分發。

負載平衡策略:Round Robin

這裡我們採用最簡單的 Round Robin 策略,該策略預設被 Nginx 支援。Round Robin 的工作原理是將請求均勻地分配到後端的各個服務例項上,從而實作負載的均衡分配。

Nginx 組態檔案與 Consul Template

首先,觀察 nginx-includes.conf 組態檔案:

location /api/v1/books {
    proxy_pass http://books-ms/api/v1/books;
    proxy_next_upstream error timeout invalid_header http_500;
}

在這個組態中,我們使用了 books-ms 作為 upstream 的名稱,而不是直接指定 IP 和埠。同時,我們增加了 proxy_next_upstream 指令,用於指定當服務傳回錯誤、超時、無效 header 或 HTTP 500 錯誤時,Nginx 將請求轉發到下一個 upstream。

接下來,我們需要定義 upstream books-ms,這透過 Consul Template 檔案 nginx-upstreams.ctmpl 來實作:

upstream books-ms {
    {{range service "books-ms" "any"}}
    server {{.Address}}:{{.Port}};
    {{end}}
}

這個範本會根據 Consul 中註冊的 books-ms 服務例項動態生成 upstream 組態。

內容解密:

  1. {{range service "books-ms" "any"}}:這行程式碼用於遍歷 Consul 中所有名為 books-ms 的服務例項,無論其狀態如何。
  2. server {{.Address}}:{{.Port}};:對於每個服務例項,將其 IP 地址和埠新增到 upstream 組態中。
  3. {{end}}:結束遍歷。

下載組態檔案並執行 Consul Template

首先,下載必要的組態檔案:

wget http://raw.githubusercontent.com/vfarcic/books-ms/master/nginx-includes.conf
wget http://raw.githubusercontent.com/vfarcic/books-ms/master/nginx-upstreams.ctmpl

然後,執行 Consul Template:

consul-template \
    -consul proxy:8500 \
    -template "nginx-upstreams.ctmpl:nginx-upstreams.conf" \
    -once
cat nginx-upstreams.conf

執行結果應該類別似於:

upstream books-ms {
    server 10.100.193.200:32768;
    server 10.100.193.200:32769;
}

這表明 Consul Template 成功地從 Consul 中檢索到了服務例項的 IP 和埠,並生成了相應的 upstream 組態。

複製組態檔案到 Proxy 節點並重啟 Nginx

將生成的組態檔案複製到 proxy 節點:

scp nginx-includes.conf proxy:/data/nginx/includes/books-ms.conf
scp nginx-upstreams.conf proxy:/data/nginx/upstreams/books-ms.conf
docker kill -s HUP nginx

驗證負載平衡與錯誤處理

透過多次請求驗證負載平衡是否生效:

curl http://proxy/api/v1/books | jq '.'
curl http://proxy/api/v1/books | jq '.'
curl http://proxy/api/v1/books | jq '.'
curl http://proxy/api/v1/books | jq '.'
docker logs nginx

觀察 Nginx 日誌,可以看到請求被均勻地分配到不同的服務例項上。

接著,模擬一個服務例項故障,驗證錯誤處理是否正常:

docker stop vagrant_app_2
curl http://proxy/api/v1/books | jq '.'
curl http://proxy/api/v1/books | jq '.'
curl http://proxy/api/v1/books | jq '.'
curl http://proxy/api/v1/books | jq '.'
docker logs nginx

結果表明,即使有服務例項停止執行,Nginx 仍然能夠正確處理請求並實作負載平衡。

代理服務的進階應用:從Nginx到HAProxy的轉換與組態

在現代化的軟體開發與佈署過程中,代理服務扮演著至關重要的角色。無論是Nginx還是HAProxy,這些工具不僅能夠提供負載平衡和高用性,還能有效地進行流量管理。本文將探討如何從Nginx轉換到HAProxy,並對其組態進行詳細解析。

Nginx與HAProxy的初步比較

Nginx和HAProxy都是免費、快速且可靠的代理服務解決方案。它們在高流量網站中表現出色,並提供了負載平衡和高用性的功能。雖然兩者有很多相似之處,但在某些特定場景下,它們的組態和使用方式卻存在著顯著的差異。

為什麼選擇HAProxy?

在某些情況下,Nginx可能無法滿足特定的需求。例如,當需要零停機時間(zero-downtime)佈署時,官方的Nginx容器可能無法提供足夠的靈活性。這時,HAProxy就成為了一個極具吸引力的替代方案。尤其是當使用像million12/haproxy這樣的容器時,它內建了inotify功能,能夠在組態檔案變更時自動重新載入HAProxy,從而避免了因重啟容器而導致的服務中斷。

組態HAProxy的步驟

1. 準備HAProxy容器

首先,我們需要準備一個執行中的HAProxy容器。透過Ansible的haproxy角色,我們可以輕鬆地完成這一步。該角色負責建立必要的目錄、複製組態檔案,並啟動HAProxy容器。

- name: Directories are present
  file:
    dest: "{{ item }}"
    state: directory
  with_items: directories
  tags: [haproxy]

- name: Container is running
  docker:
    image: million12/haproxy
    name: haproxy
    state: running
    ports: "{{ ports }}"
    volumes: /data/haproxy/config/:/etc/haproxy/
  tags: [haproxy]

2. 組態HAProxy

與Nginx不同,HAProxy不允許將組態分散到多個檔案中。因此,我們需要建立一個完整的haproxy.cfg檔案。初始時,我們只有haproxy.cfg.orig檔案,該檔案包含了預設的組態但沒有定義任何代理服務。

docker logs haproxy

執行上述命令後,我們會看到HAProxy因為找不到haproxy.cfg而無法啟動。接下來,我們需要根據實際的服務需求來生成haproxy.cfg

3. 動態生成HAProxy組態

為了實作動態組態,我們可以建立多個組態檔案片段,然後在佈署新服務時將它們拼接成完整的haproxy.cfg。這種方法模擬了Nginx中包含多個組態檔案的效果。

從Nginx遷移到HAProxy的考量

在決定從Nginx遷移到HAProxy之前,需要考慮多方面的因素,包括但不限於:

  • 效能需求:兩者在高負載下的表現。
  • 組態靈活性:是否能夠滿足特定的組態需求。
  • 社群支援和檔案:完善的檔案和活躍的社群對於解決問題至關重要。
內容解密:

本文主要探討了從Nginx轉換到HAProxy的過程和相關組態。首先介紹了兩者的基本功能和比較,接著詳細說明瞭如何組態HAProxy,包括準備容器、組態檔案的生成等步驟。最後,討論了從Nginx遷移到HAProxy時需要考慮的因素。透過本文,讀者可以更全面地瞭解這兩種代理服務工具,並做出合理的技術選型。