文/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