文/LeanCloud DevOps 王濱
javascript
雲引擎(LeanEngine)是 LeanCloud 推出的服務端託管平臺。提供了多種運行環境(Node.js, Python, PHP, Java 等)來運行服務端程序。你只須要提供服務端的業務邏輯(網站或雲函數等),而服務端的多實例負載均衡,不中斷服務的平滑升級等都由雲引擎提供支持。java
總之你能夠在 LeanCloud 上跑本身的代碼,處理 HTTP 請求。用最近的 buzzword 說,LeanEngine 是 LeanCloud 提供的一個 CaaS(Container as a Service)平臺(笑nginx
用戶的容器不可能直接暴露在公網上的,在用戶的容器與公網入口之間的東西,就是這篇文章要說明的。
git
可能不少人以爲,這不就是一個 nginx 的事麼…… 答對了!github
確實在改造前,雲引擎的入口就是幾臺 nginx,而後路由到集羣中的 hipache 上,nginx 僅僅負責 SSL 卸載以及域名綁定的工做,剩下的複雜的路由工做就交給了 hipache,由 hipache 來尋找用戶的容器到底在哪,以及路由過去。數據庫
這個架構很是簡單,可是有 3 點問題:緩存
可擴展的問題咱們已經解決了,咱們要作的就是把 nginx 搬到 Mesos 上,隨時按照需求增減實例。安全
而用戶的域名綁定不能再走這麼複雜的流程了,應該要作到數據庫中有記錄,nginx 就能正確的服務。架構
最後一條 reload 的問題,則須要 nginx 能夠動態的調整 upstream,不能依靠 reload。
app
由於 hipache 是 Node.js 項目,代碼不太好維護,也很容易出現故障(以前有一次故障,用戶請求觸發了一個 hipache 的 bug,並且異常沒有 catch 住,致使 hipache 實例不停重啓,沒法服務),所以咱們的 95 後 dev @王子亭 用 OpenResty 重寫了 hipache,新項目在咱們內部叫 hogplum。hogplum 上線後效果很好,效率明顯的比魔改 hipache 要高,代碼量也減小了很是多(如今只有幾百行),能夠很容易的掌控。
有了以前的成功經驗作背書,咱們決定把入口處的 nginx 也改爲 OpenResty,將域名綁定的工做以及 upstream 更新作成動態的。
開發的過程並無什麼值得一說的,都是一些經常使用的邏輯,經過 API 取數據什麼的。這裏列舉一些比較有意思的點:
最終的效果大概是這樣:
server {
include listen.conf;
include common/ssl-common.conf;
server_name _;
ssl_certificate path/to/leanapp.cn.crt;
ssl_certificate_key path/to/leanapp.cn.key;
root leanapp.cn/static/;
# 用來處理自定義域名的 SSL 證書
ssl_certificate_by_lua_file 'lua/leanapp/ssl.lua';
location / {
# 用來解析自定義域名與用戶容器之間的關係
set $domain '';
rewrite_by_lua_file 'lua/leanapp/domain.lua';
proxy_set_header X-LC-Domain $domain;
# 動態代理到 Mesos 集羣裏的 hogplum 實例上
dyups_interface;
set $marathon_app 'marathon:8080#lean-engine/hogplum:8080';
set $upstream '';
access_by_lua_file 'lua/marathon-app.lua';
proxy_pass http://$upstream;
}
}複製代碼
-- lua/leanapp/domain.lua
local meta = require 'leanapp.meta'
local host = ngx.var.http_host;
if not host then
ngx.redirect 'http://leanapp.cn'
return
end
local domain = meta.get_subdomain(host)
if not domain then
ngx.redirect 'http://leanapp.cn'
else
-- ngx.var.logname = host
ngx.var.logname = domain
ngx.var.domain = domain
end複製代碼
-- lua/leanapp/ssl.lua
local meta = require 'leanapp.meta'
local ngx_ssl = require 'ngx.ssl'
local host = ngx_ssl.server_name()
if not host then
return
end
local cert, key = meta.get_ssl(host)
if not cert or not key then
return
end
ngx_ssl.set_cert(cert)
ngx_ssl.set_priv_key(key)複製代碼
-- lua/marathon-app.lua
local dyups = require 'ngx.dyups'
local marathon = require 'marathon'
local die = require('utils').die
local marathon_app = ngx.var.marathon_app;
local upstream = marathon.get_upstream(marathon_app)
if not upstream then
die('No upstream for ' .. marathon_app)
end
local target = marathon_app:gsub('[/:#]', '-')
dyups.update(target, upstream)
ngx.var.upstream = target複製代碼
marathon 模塊由於代碼太長,以及 leanapp.meta 裏面有涉及敏感的信息,就不貼在這裏了。
在文章尾部附上了 marathon 路由的地址,感興趣的能夠調研下。
折騰了半天,終於把 nginx 放到 Mesos 上了。那麼用戶怎麼訪問呢?
仍是缺一個固定的入口,只不過改造以後這個入口只須要作轉發就夠了,須要的資源很是少。
由於須要經過 Proxy 協議保留客戶端的 ip 地址信息,咱們沒有用 LVS 而是用 HAProxy 來作轉發,在調優以後(減小 HAProxy 的緩衝區大小,打開 splice),HAProxy 在滿載的時候只佔用了不多的用戶態內存(10M),剩下的都是內核 TCP 佔用了。實際上每臺機器 1核1G 就能夠了,給了 2G 只是爲了安全。目前咱們的入口有4個,能夠經過 dig leanapp.cn
看到。
在 nginx 擴容/縮容後,會有一個相似以前的機制對 haproxy 作 reload,能夠看成是簡易的 marathon-lb
改造後的整個入口是這個樣子的
nginx 這一層也能夠去掉雲引擎的業務代碼,僅作 nginx 應該作的路由工做,代替如今的 API 入口(這也是最開始的目標)。
改造以後,咱們又等了一波用戶的活動,果真 nginx 開始過載。
由於 LeanCloud 的統計服務有重試機制,暫時的上報失敗不會有影響,因而咱們迅速的對統計服務進行縮容,將集羣中的資源讓給 nginx,整個過程只點了幾回鼠標。 HAProxy 由於只轉發沒有業務邏輯,消耗的資源不多,還有不少餘量,不是整個系統的瓶頸。
marathon 路由的部分已經開源,能夠戳這裏: leancloud/resty-marathon-lb