天天看點

Nginx動态配置upstream負載位址

背景

很多公司都有做動态排程系統,有些是基于mesos+docker,有些采用了google的K8s,或者是自研的系統,這些系統有一個明顯的特征就是服務執行個體的ip會頻繁更換。這種容器化的部署方式和傳統的服務部署形式不一樣,原有的服務都是部署在某些實體機或者雲主機上,這些實體機或者雲主機的ip位址不會輕易更換,這樣我們配置nginx做流量轉發的時候就可以直接寫ip。但是切換到這些容器化的系統後,服務的執行個體重新開機頻繁,每一次重新開機後執行個體的ip就會發生變化,這樣我們再用手動配置、變更後端ip的形式來做nginx的流量轉發就基本上不可行了。這時我們需要想辦法讓釋出後的執行個體ip自動更新到nginx的配置中去,并且能夠讓其自動生效。本子產品正是基于前面的應用場景,用于解決後端執行個體ip頻繁變化,無法将更新實時同步至nginx的配置中的問題。

子產品架構

前期通過調研發現,有些公司采用了etcd/consul+nginx第三方子產品(nginx-upsync-module)的方式來實作nginx零重新開機更新upstream的操作。我們内部并沒有采用etcd或者consul來存儲後端執行個體配置,而是廣泛采用了zk服務來儲存後端的配置。大部分業務都會将執行個體ip注冊到zk中去,是以我們的nginx需要從zk中拉取後端執行個體ip和端口。我們公司内部也有同學開發了nginx連接配接zk的子產品,但是該子產品是通過每個worker程序去連接配接zk,一個nginx可能有多個甚至幾十個worker程序,會造成zk的連接配接數突增,給zk叢集帶來很大的壓力。後續通過調研發現了dyups這個子產品,然後通過自己編碼實作連接配接zk,從zk中拉取配置,再通過dyups子產品的接口更新到upstream的共享記憶體,也可以實作零重新開機更新nginx的upstream清單。同時通過自己編碼實作和zk互動的邏輯,也可以控制在zk不可用時執行的邏輯。

在我們的子產品有用到dyups這個nginx子產品,dyups子產品是一個能夠直接更新正在運作的nginx的upstream清單而不需要重新reload nginx配置的子產品。這個子產品通過開放一個接口,然後外部通過這個接口發起post或者get請求,直接更新或者擷取對應upstream的後端清單。更加詳細的用法可以浏覽網址https://github.com/yzprofile/ngx_http_dyups_module檢視這個子產品的github介紹。但是由于dyups子產品隻能修改nginx的共享記憶體,不能持久化目前的upstream配置到檔案中,是以我們的子產品另外一個核心的工作就是持久化upstream配置到配置檔案中。

子產品功能

本子產品結合了我們公司常見業務的應用場景、日常使用中碰到的問題以及dyups的不足之處,主要實作了如下幾個功能:

1)擷取注冊到zk中後端清單,并對擷取到的清單資料格式化,儲存到相應的nginx配置檔案中,進行持久化

2)将儲存到檔案的後端伺服器清單通過dyups子產品的接口寫入到nginx upstream子產品的共享記憶體中,動态更新upstream裡面的後端清單

3)當zk故障時,本子產品将不再更新nginx的共享記憶體和本地nginx配置檔案,使nginx的upstream配置保持在zk故障前的狀态

4)支援讀取多個zk叢集的多個zk節點配置

子產品工作流程

Nginx動态配置upstream負載位址

子產品的使用

基礎依賴:

支援dyups子產品的nginx

python 2.6/2.7

clone子產品代碼到nginx機器上

這裡我們将子產品代碼放到/home/work目錄下

cd /home/work
git clone http://v9.git.n.xiaomi.com/liuliqiu/nginx-upstream-reloader.git
           

執行子產品源碼目錄中依賴安裝腳本(nginx-upstream-reloader/install_venv.sh),這個腳本主要用于安裝virtualvenv環境和依賴的第三方子產品

cd nginx-upstream-reloader
bash install_venv.sh
           

修改nginx-upstream-reloader子產品配置檔案

venv環境安裝完成後,修改nginx-upstream-reloader/conf目錄下的upstream_zk_nodes.conf配置檔案,這個配置檔案用于定義後端執行個體所在的目的zk叢集和zk節點以及對應nginx upstream的名字,具體的修改方法分為如下兩種情況:

1)多個後端服務注冊在一個zk叢集,按照如下配置

upstream_zk_nodes.conf
zk_servers:  zk-hadoop-test01:11000,zk-hadoop-test02:11000
zk_nodes:
    bonus-api: /web_services/com.miui.bonus.api.resin-web
           

zk_servers:後端服務注冊的zk叢集位址和端口

zk_nodes:upstream_name:後端服務注冊的zk節點路徑

當我們啟動後,子產品會拉取指定zk節點路徑下的後端清單資訊自動生成upstream_name.upstream檔案,如上述配置,我們在指定的目錄下(這個指定的目錄可以在nginx-upstream-reloader/conf/main.conf配置檔案的files_output_path選項控制,這裡我們将該選項為/home/work/nginx/site-enable)/home/work/nginx/site-enable下會生成一個bonus-api.upstream檔案,檔案的内容會如下:

upstream bonus-api {
    server ....;
    server ....;
}
           

2)多個後端伺服器注冊在不同的zk叢集

upstream_zk_nodes.conf
- zk_servers: tjwqstaging.zk.hadoop.srv:11000
  zk_nodes:
    ocean-helloworld-upstream1: /ocean/services/job.ocean-helloworld-nginx-upstream_service.ocean-helloworld-nginx-upstream_cluster.staging_pdl.oceantest_owt.inf_cop.xiaomi
    ocean-helloworld-upstream2: /ocean/services/job.ocean-helloworld-nginx-upstream_service.ocean-helloworld-nginx-upstream_cluster.staging_pdl.oceantest_owt.inf_cop.xiaomi


- zk_servers: tjwqstaging.zk.hadoop.srv:11000
  zk_nodes:
    ocean-helloworld-upstream3: /ocean/services/job.ocean-helloworld-nginx-upstream_service.ocean-helloworld-nginx-upstream_cluster.staging_pdl.oceantest_owt.inf_cop.xiaomi
           

zk_servers:後端服務注冊的zk叢集位址和端口

zk_nodes upstream_name:後端服務注冊的zk節點路徑

有同學跟我回報為什麼要用yaml格式的配置檔案,而不用json格式的配置檔案,json對格式要求沒有yaml嚴格,但是yaml的配置檔案看起層級直覺多了。

當我們啟動該子產品後,子產品會拉取指定zk節點路徑下的後端清單資訊自動生成upstream_name.upstream檔案,如上述配置,子產品在/home/work/nginx/site-enable會生成一個ocean-helloworld-upstream1.upstream、ocean-helloworld-upstream2.upstream、ocean-helloworld-upstream3.upstream三個檔案,檔案的内容會分别如下:

upstream ocean-helloworld-upstream1 {
    server ...;
    server ...;
}


upstream ocean-helloworld-upstream2 {
    server ...;
    server ...;
}


upstream ocean-helloworld-upstream3 {
    server ...;
    server ...;
}
           

修改nginx配置檔案

前面已經配置了nginx-upstream-reloader子產品連接配接zk節點擷取後端配置後,自動生成upstream配置檔案,是以我們需要在nginx中include這些upstream配置檔案在server塊中才可以使用這些upstream。目前由于dyups子產品的限制,需要将upstream_name設定為一個變量,然後在proxy_pass指令中使用這個變量配置轉發,具體可以參考下面的配置:

include /home/work/nginx/site-enable/ocean-helloword-upstream1.upstream;
include /home/work/nginx/site-enable/ocean-helloword-upstream2.upstream;
include /home/work/nginx/site-enable/ocean-helloword-upstream3.upstream;

server {
        listen   80;
        location /helloworld1 {
                set $ups1 ocean-helloword-upstream1;
                proxy_pass http://$ups;
        }
        location /helloworld2 {
                set $ups2 ocean-helloword-upstream2;
                proxy_pass http://$ups2;
        }
        location /helloworld3 {
                set $ups3 ocean-helloword-upstream3;
                proxy_pass http://$ups3;
        }
}
           

修改nginx配置檔案,開啟dyups接口

這裡添加一個單獨的server,監聽本地位址的14443端口

server{
    listen 127.0.0.1:14443;
    server_name _;
    location / {
        dyups_interface;
    }
}
           

啟動zk動态發現子產品和nginx

這裡需要先執行nginx-upstream-reloader/start.sh檔案,啟動nginx-upstream-reloader子產品,然後再啟動nginx,因為當我們還沒有啟動upstream-reloader子產品時,upstream配置檔案還未生成,但我們nginx配置檔案中已經include這些upstream配置檔案,這時啟動nginx就會報錯

bash nginx-upstream-reloader/start.sh
/home/work/nginx/sbin/nginx #這裡假設我們的nginx安裝在/home/work/nginx/目錄下
           

相容性

目前該子產品已經在centos6和centos7上測試通過,适用于容器和實體機。

繼續閱讀