Nginx 動態發現方案與實踐

小米運維發表於2018-11-08
本文主要介紹了適用於nginx的zk動態後端發現模組(nginx-upstream-reloader)及其使用方法。
上篇文章回顧:SOAR 101 快速入門指南

一、背景

很多公司都有做動態排程系統,有些是基於 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 動態發現方案與實踐

五、模組的使用

基礎依賴:

支援 dyups 模組的 nginx

python 2.6/2.7

1、clone 模組程式碼到 nginx 機器上

這裡我們將模組程式碼放到/home/work 目錄下

cd /home/work
git clone http://v9.git.n.xiaomi.com/liuliqiu/nginx-upstream-reloader.git複製程式碼

2、執行模組原始碼目錄中依賴安裝指令碼(nginx-upstream-reloader/install_venv.sh),這個指令碼主要用於安裝virtualvenv環境和依賴的第三方模組

cd nginx-upstream-reloader
bash install_venv.sh複製程式碼

3、修改 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 ...;
}複製程式碼

4、修改 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;
        }
}複製程式碼

5、修改 nginx 配置檔案,開啟 dyups 介面

這裡新增一個單獨的 server,監聽本地地址的14443埠

server{
    listen 127.0.0.1:14443;
    server_name _;
    location / {
        dyups_interface;
    }
}複製程式碼

6、啟動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 上測試通過,適用於容器和物理機。

本文首發於公眾號“小米運維”,點選檢視原文


相關文章