SlideShare a Scribd company logo
Using ngx_lua in UPYUN 2
Monkey Zhang (timebug)
2015.11 @ Beijing OpenResty Con
Using ngx_lua in UPYUN 2
Email: timebug.info@gmail.com
Github: https://github.com/timebug
A Systems Engineer at UPYUN
$ ./configure --prefix=/opt/nginx 
--add-module=/path/to/lua-nginx-module
http {
server {
listen 8080;
location /add {
set $res '';
rewrite_by_lua '
local a = tonumber(ngx.var.arg_a) or 0
local b = tonumber(ngx.var.arg_b) or 0
ngx.var.res = a + b
';
content_by_lua '
ngx.say(ngx.var.res)
';
}
}
}
$ curl 'http://localhost:8080/add?a=6&b=7'
13
UPYUN CDN & API is built on top of
NGINX with ngx_lua
Why not use OpenResty?
40000+ lines Lua
lua-resty-sniff
lua-resty-limit-req
lua-resty-rewrite lua-resty-argutils
lua-resty-dbcache
lua-resty-anticc
lua-resty-combo
lua-resty-httpipe
lua-resty-checkups
lua-resty-17monip
lua-resty-httproxy
. . .
Project Structure:
NGINX with ngx_lua
~/project/upyun/marco
!"" Makefile
!"" README.md
!"" addons
#   %"" ngx_upxxx_module
!"" deps
!"" nginx
#   !"" app
#   #   !"" etc
#   #   # %"" config.lua
#   #   !"" lib
#   #   #   %"" resty
#   #   #   %"" httpipe.lua
#   #   %"" src
#   #   !"" modules
#   #   # %"" referer.lua
#   # !"" marco_init.lua
#   # %"" marco_log.lua
#   %"" conf
#     %"" nginx.conf
!"" patches
!"" tests
%"" util
!"" deps
!"" lua-releng
%"" ver.cfg
/usr/local/marco
!"" luajit
%"" nginx
   !"" app
   #   !"" etc
   #   # %"" config.lua
   #   !"" lib
   #   # !"" cjson.so
   #   #   %"" resty
   #   #   %"" httpipe.lua
   #   %"" src
   #   !"" modules
   #   # %"" referer.lua
   # !"" marco_init.lua
   # %"" marco_log.lua
   !"" conf
   #   %"" nginx.conf
   !"" html
   !"" logs
   %"" sbin
%"" nginx
make install
Project Structure:
Quick Start & Run
make deps
make configure
make
make install
util/ver.cfg
V_PCRE=8.34
V_NGINX=1.7.10
V_LUAJIT=2.1-20150223
V_LUA_CJSON=2.1.0
V_NGX_DEVEL_KIT=0.2.19
V_NGX_LUA_MODULE=0.9.15
Makefile
INSTALL_LIBDIR=$(PREFIX)/nginx/app/lib/
configure: deps luajit
@echo "==== Configuring Nginx $(V_NGINX) ===="
cd $(NGINX_DIR) && ./configure 
--with-pcre=$(ROOTDIR)/deps/pcre-$(V_PCRE) 
--with-ld-opt="-Wl,-rpath,$(LUAJIT_LIB),-rpath,$(INSTALL_LIBDIR)" 
--add-module=$(ROOTDIR)/deps/ngx_devel_kit-$(V_NGX_DEVEL_KIT) 
--add-module=$(ROOTDIR)/deps/lua-nginx-module-$(V_NGX_LUA_MODULE) 
--prefix=$(PREFIX)/nginx
@echo "==== Successfully configure Nginx $(V_NGINX) ===="
Project Structure:
Development & Test
make dev
make test
Makefile
test:
util/lua-releng
py.test tests/test_marco.py
tests/test_marco.py
class TestMarco(unittest.TestCase):
@no_error_log(["error"])
@grep_error_log(level=["info"],
log_pattern="SSL_do_handshake[(][)] failed",
log_out=["SSL_do_handshake() failed"])
def test_ssl_handler_no_certificate(self):
fake_resp = self.curl_ssl(sni="fake.com", verbose=True)
self.assertTrue("alert handshake failure" in fake_resp)
nginx.conf service
server_name *.b0.upaiyun.com Custom Domain Binding
valid_referers, allow, deny
Custom Antileech Rules and Redirect:
ip, user-agent, referer, token etc.
expires 7d
Custom Cache Control:
support specific URI rules etc.
ssl_certificate* ssl_stapling* Custom SSL
upstream { server 127.0.0.1 }
Custom CDN Origin:
support multi-network routing etc.
max_fails=3 fail_timeout=30s
health_check (*)
Custom Health Check Strategy:
passive, active
round-robin, ip_hash, hash (1.7.2+) Custom Load Balancing Strategy
rewrite Custom URL rewrite
… …
UPYUN CDN
130+ Edge Nodes
upstream blog.upyun.com {
server 192.168.11.1:8080 weight=1 max_fails=10 fail_timeout=30s;
server 192.168.11.2:8080 weight=2 max_fails=10 fail_timeout=30s;
server 192.168.11.3:8080 weight=1 max_fails=10 fail_timeout=30s backup;
proxy_next_upstream error timeout http_500;
proxy_next_upstream_tries 2;
}
Lua Upstream Configuration:
lua-resty-checkups
-- app/etc/config.lua
_M.global = {
checkup_timer_interval = 5,
checkup_timer_overtime = 60,
}
_M.api = {
timeout = 2,
typ = "general", -- http, redis, mysql etc.
cluster = {
{ -- level 1
try = 2,
servers = {
{ host = "192.168.11.1", port = 8080, weight = 1 },
{ host = "192.168.11.2", port = 8080, weight = 2 },
}
},
{ -- level 2
servers = {
{ host = "192.168.11.3", port = 8080, weight = 1 },
}
},
},
}
cosocket
redis
http
mysql
memcached
…
Lua Upstream Health Checks:
lua-resty-checkups
access_by_lua '
local checkups = require "resty.checkups"
-- only one timer is active among all the nginx workers
checkups.create_checker()
';
Lua Upstream Health Checks:
checkups with nginx.conf
-- app/etc/config.lua
_M.global = {
checkup_timer_interval = 5,
checkup_timer_overtime = 60,
ups_status_sync_enable = true,
ups_status_timer_interval = 2,
}
_M.blog = {
cluster = {
{ -- level 1
try = 2,
upstream = "blog.upyun.com",
},
{ -- level 2
upstream = "blog.upyun.com",
upstream_only_backup = true,
},
},
}
lua-upstream-nginx-module
Lua Upstream Health Checks:
checkups with status page
Lua Upstream Dynamically:
Configure Everything as JSON
{
"bucket:upblog": [
{
"fail_timeout": 30,
"host": "192.168.11.1",
"max_fails": 3,
"port": 8080,
"weight": 1
},
{
"fail_timeout": 30,
"host": "192.168.11.2",
"max_fails": 3,
"port": 8080,
"weight": 2
},
{
"backup": true,
"fail_timeout": 30,
"host": "192.168.11.3",
"max_fails": 3,
"port": 8080,
"weight": 1
}
]
}
master slave
Lua Metadata Cache:
lua-resty-shcache
-- app/src/modules/metadata.lua
local shcache = require "resty.shcache"
function _M.get_metadata(bucket)
local lookup_metadata = function ()
-- fetch from redis
return res
end
local cache_data = shcache:new(
ngx.shared.metadata,
{ external_lookup = lookup_metadata,
encode = cmsgpack.pack,
decode = cmsgpack.unpack,
},
{ positive_ttl = cache_positive_ttl,
negative_ttl = cache_negative_ttl,
name = "metadata",
})
-- local key = ...
local data, _ = cache_data:load(key)
if not data then
return
end
return data
end
Lua Metadata Cache:
lua-resty-dbcache
HIT
STALE
HIT_NEGATIVE
NO_DATA
MISS
??? (NET_ERR)
Lua Upstream Dynamically:
maintaining internal state
max_fails=10
fail_timeout=30s
lua-resty-lrucache
lua-resty-shcache
{
"bucket:upblog": [ . . . ]
}
Lua Upstream Load Balancing:
round-robin with weight
function _M.reset_round_robin_state(cls)
local rr = { index = 0, current_weight = 0 }
rr.gcd, rr.max_weight, rr.weight_sum = _M.calc_gcd_weight(cls.servers)
cls.rr = rr
end
cluster = {
{
servers = {
{ host = "127.0.0.1", port = 12351, weight = 1 },
{ host = "127.0.0.1", port = 12352, weight = 4 },
{ host = "127.0.0.1", port = 12353, weight = 3 },
{ host = "127.0.0.1", port = 12355, weight = 6 },
}
}
}
rr.index = 0
rr.current_weight = 0
rr.gcd = 1
rr.max_weight = 6
rr.weight_sum = 14
Lua Upstream Load Balancing:
round-robin with weight
local bad_servers = {}
for i = 1, #cls.servers, 1 do
local srv, index, err = _M.select_round_robin_server(cls, verify_server_status, bad_servers)
if not srv then
return nil, err
else
local res, _ = callback(srv)
if res then
if srv.effective_weight ~= srv.weight then
srv.effective_weight = srv.weight
_M.reset_round_robin_state(cls)
end
return res
end
if srv.effective_weight > 1 then
srv.effective_weight = floor(sqrt(srv.effective_weight))
_M.reset_round_robin_state(cls)
end
bad_servers[index] = true
end
end
local try_servers_by_round_robin = function(cls, verify_server_status, callback)
Lua Upstream Load Balancing:
round-robin with weight
function _M.select_round_robin_server(cls, verify_server_status, bad_servers)
local rr = cls.rr
local servers = cls.servers
local index = rr.index
local current_weight = rr.current_weight
local gcd = rr.gcd
local max_weight = rr.max_weight
local weight_sum = rr.weight_sum
local failed = 1
repeat
until failed > weight_sum
TA
LK
IS
C
H
EA
P
Lua Upstream Load Balancing:
round-robin with weight
index = index % #servers + 1
if index == 1 then
current_weight = current_weight - gcd
if current_weight <= 0 then current_weight = max_weight end
end
local srv = servers[index]
if srv.effective_weight >= current_weight then
cls.rr.index, cls.rr.current_weight = index, current_weight
if not bad_servers[index] then
if verify_server_status then
if verify_server_status(srv) then
return srv, index
else
if srv.effective_weight > 1 then
srv.effective_weight, index, current_weight, failed_count = 1, 0, 0, 0
_M.reset_round_robin_state(cls)
gcd, max_weight, weight_sum = cls.rr.gcd, cls.rr.max_weight, cls.rr.weight_sum
end
failed = failed + 1
end
else
return srv, index
end
else
failed = failed + 1
end
end
repeat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . until failed > weight_sum
Lua Upstream Load Balancing:
round-robin with weight
local verify_server_status = function(srv)
local peer_key = _gen_key(srv)
local peer_status = cjson.decode(state:get(PEER_STATUS_PREFIX .. peer_key))
if peer_status == nil or peer_status.status ~= _M.STATUS_ERR then
return true
end
return
end
STATUS_OK = 0
STATUS_UNSTABLE = 1
STATUS_ERR = 2
Lua Upstream Load Balancing:
=== TEST 1: round-robin single level
--- http_config eval
"$::HttpConfig" . "$::InitConfig"
--- config
location = /t {
content_by_lua '
local checkups = require "resty.checkups"
checkups.create_checker()
ngx.sleep(2)
local dict = {
[12351] = "A",
[12352] = "B",
[12353] = "C",
[12355] = "E",
}
local cb_ok = function(srv)
ngx.print(dict[srv.port])
return 1
end
for i = 1, 30, 1 do
local ok, err = checkups.ready_ok("single_level", cb_ok)
if err then
ngx.say(err)
end
end
';
}
--- request
GET /t
--- response_body: EEBEBCEBCEABCEEEBEBCEBCEABCEEE
_M.single_level = {
cluster = {
{
servers = {
{ host = "127.0.0.1", port = 12351, weight = 1 },
{ host = "127.0.0.1", port = 12352, weight = 4 },
{ host = "127.0.0.1", port = 12353, weight = 3 },
{ host = "127.0.0.1", port = 12355, weight = 6 },
}
}
}
}
EEBEBCEBCEABCE
. . . . . .
Lua Upstream Load Balancing:
consistent-hash and more
try_servers_by_round_robin
try_cluster_by_round_robin
cluster = {
{
servers = {
{ host = "127.0.0.1", port = 12351, weight = 1 },
{ host = "127.0.0.1", port = 12352, weight = 4 },
{ host = "127.0.0.1", port = 12353, weight = 3 },
{ host = "127.0.0.1", port = 12355, weight = 6 },
}
},
{
servers = {
{ host = "127.0.0.1", port = 12354, weight = 1 },
{ host = "127.0.0.1", port = 12356, weight = 2 },
}
}
}
try_servers_by_consistent_hash
try_cluster_by_consistent_hash
Using ngx_lua in UPYUN 2
"$WHEN($MATCH($_URI, '^/foo/.*'))$ADD_REQ_HEADER(X-Foo, bar)"
tianchaijz:
Marco: I GOT IT !
Edge Server
Lua Custom URL rewrite:
lua-resty-rewrite | variables
$_HOST
$_HOST_n
$_GET_name
$_COOKIE_name
$_POST_name
$_HEADER_name
$_RANDOM$_RANDOM_n
$_URI
$_QUERY
$_SYM_sym
$_SCHEME$_METHOD
Lua Custom URL rewrite:
lua-resty-rewrite | functions
$ALL(E1, E2, ...)
$SUB(E1, from, to)
$PCALL(E)
$DECODE_BASE64(E)
$WHEN(E1, E2, ...)
$ADD_REQ_HEADER(E1, E2)$GT(E1, E2)
$MATCH(E1, E2)
$LOWER(E)
$UPPER(E)
$ENCODE_BASE64(E)
$GE(E1, E2)
$EQ(E1, E2)
$ANY(E1, E2, ...)
$DEL_REQ_HEADER(E1)
$ADD_RSP_HEADER(E1, E2)
Lua Custom URL rewrite:
lua-resty-rewrite | break
rewrite /download/(.*)/(.*) /$1/$2.mp3?_session=$_COOKIE_id?
rewrite /download/(.*)/(.*) /$1/$2.mp3?user=$_HOST_1 break
. . .
See More: https://github.com/upyun/docs/issues/5
http://io.upyun.com/2015/03/09/hello-world/?foo=bar
[scheme] [host] [path] [query]
Lua Custom Cache-Control:
Using specific URI rules
location ^~ /www/ {
if ($query_string ~* "foo=bar") {
expires 300s;
}
}
location ^~ /images/ {
expires 1h;
}
location ~* .jpg$ {
expires 1d;
}
Lua Custom SSL:
Certificates Load & OCSP stapling
server {
listen 443 ssl;
server_name upyun.com;
ssl_certificate upyun.com.pem;
ssl_certificate_key upyun.com.key;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/private/ca-certs.pem;
}
Lua Custom Logging:
lua-resty-logger-socket
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
server {
access_log /path/to/access.log combined buffer=4096;
. . .
}
EdgeServer
UPYUNLOG
bucket:hbimg = {"enable":true,"ratio":0.1}
logger.log(cjson.encode(log_msg_table) .. "n")
UPYUN LOG Platform:
HAProxy + Heka + Kafka + Elasticsearch + Kibana
UPYUN API
location /upload {
proxy_request_buffering off;
. . .
}
Lua Streaming Upload
ngx.req.init_body()
ngx.req.append_body(chunk)
ngx.req.finish_body()
Lua CDN
Lua WAF
Lua SSL
Lua API
Join our team
©2012-2014 Trybiane
Thanks
Nginx ngx_lua agentzh Lua
blog.cloudflare.com Openresty LuaJIT Github
Maxim Dounin Igor Sysoev chaoslawful
https://groups.google.com/forum/#!forum/OpenResty
Ansible Michael Pall Open source
…
Q & A

More Related Content

PDF
Using ngx_lua in upyun 2
PDF
Using ngx_lua in UPYUN
PDF
Roll Your Own API Management Platform with nginx and Lua
PDF
Lua tech talk
PDF
Devinsampa nginx-scripting
PDF
Bootstrapping multidc observability stack
PDF
Observability with Consul Connect
PDF
RestMQ - HTTP/Redis based Message Queue
Using ngx_lua in upyun 2
Using ngx_lua in UPYUN
Roll Your Own API Management Platform with nginx and Lua
Lua tech talk
Devinsampa nginx-scripting
Bootstrapping multidc observability stack
Observability with Consul Connect
RestMQ - HTTP/Redis based Message Queue

What's hot (20)

PDF
Replacing Squid with ATS
PDF
Failsafe Mechanism for Yahoo Homepage
PDF
Top Node.js Metrics to Watch
ODP
Integrating icinga2 and the HashiCorp suite
PDF
PDF
Facebook的缓存系统
PPTX
My sql failover test using orchestrator
PDF
glance replicator
PDF
Bootstrapping multidc observability stack
PPTX
Query logging with proxysql
PDF
Redis as a message queue
PPTX
Creating Reusable Puppet Profiles
KEY
Railsconf2011 deployment tips_for_slideshare
PDF
Static Typing in Vault
PPTX
ProxySQL & PXC(Query routing and Failover Test)
PDF
Securing Prometheus exporters using HashiCorp Vault
PPTX
SCALE 15x Minimizing PostgreSQL Major Version Upgrade Downtime
PDF
Puppet and the HashiStack
PPTX
MySQL Monitoring using Prometheus & Grafana
PDF
tokyo.vcl発表資料(varnish+squid)
Replacing Squid with ATS
Failsafe Mechanism for Yahoo Homepage
Top Node.js Metrics to Watch
Integrating icinga2 and the HashiCorp suite
Facebook的缓存系统
My sql failover test using orchestrator
glance replicator
Bootstrapping multidc observability stack
Query logging with proxysql
Redis as a message queue
Creating Reusable Puppet Profiles
Railsconf2011 deployment tips_for_slideshare
Static Typing in Vault
ProxySQL & PXC(Query routing and Failover Test)
Securing Prometheus exporters using HashiCorp Vault
SCALE 15x Minimizing PostgreSQL Major Version Upgrade Downtime
Puppet and the HashiStack
MySQL Monitoring using Prometheus & Grafana
tokyo.vcl発表資料(varnish+squid)
Ad

Similar to Using ngx_lua in UPYUN 2 (20)

PDF
Apache MXNet Distributed Training Explained In Depth by Viacheslav Kovalevsky...
PDF
Puppet Camp 2012
PDF
Burn down the silos! Helping dev and ops gel on high availability websites
PDF
Salesforce at Stacki Atlanta Meetup February 2016
PDF
Debugging: Rules And Tools - PHPTek 11 Version
PDF
ByPat博客出品Lvs+keepalived
PDF
Building better Node.js applications on MariaDB
PDF
Understanding OpenStack Deployments - PuppetConf 2014
PDF
Service worker: discover the next web game changer
KEY
KEY
Django Celery
ODP
Владимир Перепелица "Модули"
KEY
PDF
Relayd: a load balancer for OpenBSD
KEY
Cooking with Chef
PDF
StackiFest16: Stacki 1600+ Server Journey - Dave Peterson, Salesforce
PPTX
Stacki - The1600+ Server Journey
PDF
Thijs Feryn - Leverage HTTP to deliver cacheable websites - Codemotion Milan ...
PDF
8 devstack beyond_hello-world
Apache MXNet Distributed Training Explained In Depth by Viacheslav Kovalevsky...
Puppet Camp 2012
Burn down the silos! Helping dev and ops gel on high availability websites
Salesforce at Stacki Atlanta Meetup February 2016
Debugging: Rules And Tools - PHPTek 11 Version
ByPat博客出品Lvs+keepalived
Building better Node.js applications on MariaDB
Understanding OpenStack Deployments - PuppetConf 2014
Service worker: discover the next web game changer
Django Celery
Владимир Перепелица "Модули"
Relayd: a load balancer for OpenBSD
Cooking with Chef
StackiFest16: Stacki 1600+ Server Journey - Dave Peterson, Salesforce
Stacki - The1600+ Server Journey
Thijs Feryn - Leverage HTTP to deliver cacheable websites - Codemotion Milan ...
8 devstack beyond_hello-world
Ad

Recently uploaded (20)

PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PDF
Empathic Computing: Creating Shared Understanding
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Unlocking AI with Model Context Protocol (MCP)
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
KodekX | Application Modernization Development
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PPTX
Big Data Technologies - Introduction.pptx
PPTX
A Presentation on Artificial Intelligence
PPTX
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PPTX
Cloud computing and distributed systems.
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Approach and Philosophy of On baking technology
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Review of recent advances in non-invasive hemoglobin estimation
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Mobile App Security Testing_ A Comprehensive Guide.pdf
CIFDAQ's Market Insight: SEC Turns Pro Crypto
Empathic Computing: Creating Shared Understanding
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Unlocking AI with Model Context Protocol (MCP)
The AUB Centre for AI in Media Proposal.docx
KodekX | Application Modernization Development
Chapter 3 Spatial Domain Image Processing.pdf
Big Data Technologies - Introduction.pptx
A Presentation on Artificial Intelligence
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Cloud computing and distributed systems.
MYSQL Presentation for SQL database connectivity
Approach and Philosophy of On baking technology
Per capita expenditure prediction using model stacking based on satellite ima...
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Review of recent advances in non-invasive hemoglobin estimation

Using ngx_lua in UPYUN 2

  • 1. Using ngx_lua in UPYUN 2 Monkey Zhang (timebug) 2015.11 @ Beijing OpenResty Con
  • 4. $ ./configure --prefix=/opt/nginx --add-module=/path/to/lua-nginx-module http { server { listen 8080; location /add { set $res ''; rewrite_by_lua ' local a = tonumber(ngx.var.arg_a) or 0 local b = tonumber(ngx.var.arg_b) or 0 ngx.var.res = a + b '; content_by_lua ' ngx.say(ngx.var.res) '; } } } $ curl 'http://localhost:8080/add?a=6&b=7' 13
  • 5. UPYUN CDN & API is built on top of NGINX with ngx_lua Why not use OpenResty?
  • 6. 40000+ lines Lua lua-resty-sniff lua-resty-limit-req lua-resty-rewrite lua-resty-argutils lua-resty-dbcache lua-resty-anticc lua-resty-combo lua-resty-httpipe lua-resty-checkups lua-resty-17monip lua-resty-httproxy . . .
  • 7. Project Structure: NGINX with ngx_lua ~/project/upyun/marco !"" Makefile !"" README.md !"" addons #   %"" ngx_upxxx_module !"" deps !"" nginx #   !"" app #   #   !"" etc #   #   # %"" config.lua #   #   !"" lib #   #   #   %"" resty #   #   #   %"" httpipe.lua #   #   %"" src #   #   !"" modules #   #   # %"" referer.lua #   # !"" marco_init.lua #   # %"" marco_log.lua #   %"" conf #     %"" nginx.conf !"" patches !"" tests %"" util !"" deps !"" lua-releng %"" ver.cfg /usr/local/marco !"" luajit %"" nginx    !"" app    #   !"" etc    #   # %"" config.lua    #   !"" lib    #   # !"" cjson.so    #   #   %"" resty    #   #   %"" httpipe.lua    #   %"" src    #   !"" modules    #   # %"" referer.lua    # !"" marco_init.lua    # %"" marco_log.lua    !"" conf    #   %"" nginx.conf    !"" html    !"" logs    %"" sbin %"" nginx make install
  • 8. Project Structure: Quick Start & Run make deps make configure make make install util/ver.cfg V_PCRE=8.34 V_NGINX=1.7.10 V_LUAJIT=2.1-20150223 V_LUA_CJSON=2.1.0 V_NGX_DEVEL_KIT=0.2.19 V_NGX_LUA_MODULE=0.9.15 Makefile INSTALL_LIBDIR=$(PREFIX)/nginx/app/lib/ configure: deps luajit @echo "==== Configuring Nginx $(V_NGINX) ====" cd $(NGINX_DIR) && ./configure --with-pcre=$(ROOTDIR)/deps/pcre-$(V_PCRE) --with-ld-opt="-Wl,-rpath,$(LUAJIT_LIB),-rpath,$(INSTALL_LIBDIR)" --add-module=$(ROOTDIR)/deps/ngx_devel_kit-$(V_NGX_DEVEL_KIT) --add-module=$(ROOTDIR)/deps/lua-nginx-module-$(V_NGX_LUA_MODULE) --prefix=$(PREFIX)/nginx @echo "==== Successfully configure Nginx $(V_NGINX) ===="
  • 9. Project Structure: Development & Test make dev make test Makefile test: util/lua-releng py.test tests/test_marco.py tests/test_marco.py class TestMarco(unittest.TestCase): @no_error_log(["error"]) @grep_error_log(level=["info"], log_pattern="SSL_do_handshake[(][)] failed", log_out=["SSL_do_handshake() failed"]) def test_ssl_handler_no_certificate(self): fake_resp = self.curl_ssl(sni="fake.com", verbose=True) self.assertTrue("alert handshake failure" in fake_resp)
  • 10. nginx.conf service server_name *.b0.upaiyun.com Custom Domain Binding valid_referers, allow, deny Custom Antileech Rules and Redirect: ip, user-agent, referer, token etc. expires 7d Custom Cache Control: support specific URI rules etc. ssl_certificate* ssl_stapling* Custom SSL upstream { server 127.0.0.1 } Custom CDN Origin: support multi-network routing etc. max_fails=3 fail_timeout=30s health_check (*) Custom Health Check Strategy: passive, active round-robin, ip_hash, hash (1.7.2+) Custom Load Balancing Strategy rewrite Custom URL rewrite … …
  • 13. upstream blog.upyun.com { server 192.168.11.1:8080 weight=1 max_fails=10 fail_timeout=30s; server 192.168.11.2:8080 weight=2 max_fails=10 fail_timeout=30s; server 192.168.11.3:8080 weight=1 max_fails=10 fail_timeout=30s backup; proxy_next_upstream error timeout http_500; proxy_next_upstream_tries 2; }
  • 14. Lua Upstream Configuration: lua-resty-checkups -- app/etc/config.lua _M.global = { checkup_timer_interval = 5, checkup_timer_overtime = 60, } _M.api = { timeout = 2, typ = "general", -- http, redis, mysql etc. cluster = { { -- level 1 try = 2, servers = { { host = "192.168.11.1", port = 8080, weight = 1 }, { host = "192.168.11.2", port = 8080, weight = 2 }, } }, { -- level 2 servers = { { host = "192.168.11.3", port = 8080, weight = 1 }, } }, }, } cosocket redis http mysql memcached …
  • 15. Lua Upstream Health Checks: lua-resty-checkups access_by_lua ' local checkups = require "resty.checkups" -- only one timer is active among all the nginx workers checkups.create_checker() ';
  • 16. Lua Upstream Health Checks: checkups with nginx.conf -- app/etc/config.lua _M.global = { checkup_timer_interval = 5, checkup_timer_overtime = 60, ups_status_sync_enable = true, ups_status_timer_interval = 2, } _M.blog = { cluster = { { -- level 1 try = 2, upstream = "blog.upyun.com", }, { -- level 2 upstream = "blog.upyun.com", upstream_only_backup = true, }, }, } lua-upstream-nginx-module
  • 17. Lua Upstream Health Checks: checkups with status page
  • 18. Lua Upstream Dynamically: Configure Everything as JSON { "bucket:upblog": [ { "fail_timeout": 30, "host": "192.168.11.1", "max_fails": 3, "port": 8080, "weight": 1 }, { "fail_timeout": 30, "host": "192.168.11.2", "max_fails": 3, "port": 8080, "weight": 2 }, { "backup": true, "fail_timeout": 30, "host": "192.168.11.3", "max_fails": 3, "port": 8080, "weight": 1 } ] } master slave
  • 19. Lua Metadata Cache: lua-resty-shcache -- app/src/modules/metadata.lua local shcache = require "resty.shcache" function _M.get_metadata(bucket) local lookup_metadata = function () -- fetch from redis return res end local cache_data = shcache:new( ngx.shared.metadata, { external_lookup = lookup_metadata, encode = cmsgpack.pack, decode = cmsgpack.unpack, }, { positive_ttl = cache_positive_ttl, negative_ttl = cache_negative_ttl, name = "metadata", }) -- local key = ... local data, _ = cache_data:load(key) if not data then return end return data end
  • 21. Lua Upstream Dynamically: maintaining internal state max_fails=10 fail_timeout=30s lua-resty-lrucache lua-resty-shcache { "bucket:upblog": [ . . . ] }
  • 22. Lua Upstream Load Balancing: round-robin with weight function _M.reset_round_robin_state(cls) local rr = { index = 0, current_weight = 0 } rr.gcd, rr.max_weight, rr.weight_sum = _M.calc_gcd_weight(cls.servers) cls.rr = rr end cluster = { { servers = { { host = "127.0.0.1", port = 12351, weight = 1 }, { host = "127.0.0.1", port = 12352, weight = 4 }, { host = "127.0.0.1", port = 12353, weight = 3 }, { host = "127.0.0.1", port = 12355, weight = 6 }, } } } rr.index = 0 rr.current_weight = 0 rr.gcd = 1 rr.max_weight = 6 rr.weight_sum = 14
  • 23. Lua Upstream Load Balancing: round-robin with weight local bad_servers = {} for i = 1, #cls.servers, 1 do local srv, index, err = _M.select_round_robin_server(cls, verify_server_status, bad_servers) if not srv then return nil, err else local res, _ = callback(srv) if res then if srv.effective_weight ~= srv.weight then srv.effective_weight = srv.weight _M.reset_round_robin_state(cls) end return res end if srv.effective_weight > 1 then srv.effective_weight = floor(sqrt(srv.effective_weight)) _M.reset_round_robin_state(cls) end bad_servers[index] = true end end local try_servers_by_round_robin = function(cls, verify_server_status, callback)
  • 24. Lua Upstream Load Balancing: round-robin with weight function _M.select_round_robin_server(cls, verify_server_status, bad_servers) local rr = cls.rr local servers = cls.servers local index = rr.index local current_weight = rr.current_weight local gcd = rr.gcd local max_weight = rr.max_weight local weight_sum = rr.weight_sum local failed = 1 repeat until failed > weight_sum TA LK IS C H EA P
  • 25. Lua Upstream Load Balancing: round-robin with weight index = index % #servers + 1 if index == 1 then current_weight = current_weight - gcd if current_weight <= 0 then current_weight = max_weight end end local srv = servers[index] if srv.effective_weight >= current_weight then cls.rr.index, cls.rr.current_weight = index, current_weight if not bad_servers[index] then if verify_server_status then if verify_server_status(srv) then return srv, index else if srv.effective_weight > 1 then srv.effective_weight, index, current_weight, failed_count = 1, 0, 0, 0 _M.reset_round_robin_state(cls) gcd, max_weight, weight_sum = cls.rr.gcd, cls.rr.max_weight, cls.rr.weight_sum end failed = failed + 1 end else return srv, index end else failed = failed + 1 end end repeat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . until failed > weight_sum
  • 26. Lua Upstream Load Balancing: round-robin with weight local verify_server_status = function(srv) local peer_key = _gen_key(srv) local peer_status = cjson.decode(state:get(PEER_STATUS_PREFIX .. peer_key)) if peer_status == nil or peer_status.status ~= _M.STATUS_ERR then return true end return end STATUS_OK = 0 STATUS_UNSTABLE = 1 STATUS_ERR = 2
  • 27. Lua Upstream Load Balancing: === TEST 1: round-robin single level --- http_config eval "$::HttpConfig" . "$::InitConfig" --- config location = /t { content_by_lua ' local checkups = require "resty.checkups" checkups.create_checker() ngx.sleep(2) local dict = { [12351] = "A", [12352] = "B", [12353] = "C", [12355] = "E", } local cb_ok = function(srv) ngx.print(dict[srv.port]) return 1 end for i = 1, 30, 1 do local ok, err = checkups.ready_ok("single_level", cb_ok) if err then ngx.say(err) end end '; } --- request GET /t --- response_body: EEBEBCEBCEABCEEEBEBCEBCEABCEEE _M.single_level = { cluster = { { servers = { { host = "127.0.0.1", port = 12351, weight = 1 }, { host = "127.0.0.1", port = 12352, weight = 4 }, { host = "127.0.0.1", port = 12353, weight = 3 }, { host = "127.0.0.1", port = 12355, weight = 6 }, } } } } EEBEBCEBCEABCE . . . . . .
  • 28. Lua Upstream Load Balancing: consistent-hash and more try_servers_by_round_robin try_cluster_by_round_robin cluster = { { servers = { { host = "127.0.0.1", port = 12351, weight = 1 }, { host = "127.0.0.1", port = 12352, weight = 4 }, { host = "127.0.0.1", port = 12353, weight = 3 }, { host = "127.0.0.1", port = 12355, weight = 6 }, } }, { servers = { { host = "127.0.0.1", port = 12354, weight = 1 }, { host = "127.0.0.1", port = 12356, weight = 2 }, } } } try_servers_by_consistent_hash try_cluster_by_consistent_hash
  • 31. Lua Custom URL rewrite: lua-resty-rewrite | variables $_HOST $_HOST_n $_GET_name $_COOKIE_name $_POST_name $_HEADER_name $_RANDOM$_RANDOM_n $_URI $_QUERY $_SYM_sym $_SCHEME$_METHOD
  • 32. Lua Custom URL rewrite: lua-resty-rewrite | functions $ALL(E1, E2, ...) $SUB(E1, from, to) $PCALL(E) $DECODE_BASE64(E) $WHEN(E1, E2, ...) $ADD_REQ_HEADER(E1, E2)$GT(E1, E2) $MATCH(E1, E2) $LOWER(E) $UPPER(E) $ENCODE_BASE64(E) $GE(E1, E2) $EQ(E1, E2) $ANY(E1, E2, ...) $DEL_REQ_HEADER(E1) $ADD_RSP_HEADER(E1, E2)
  • 33. Lua Custom URL rewrite: lua-resty-rewrite | break rewrite /download/(.*)/(.*) /$1/$2.mp3?_session=$_COOKIE_id? rewrite /download/(.*)/(.*) /$1/$2.mp3?user=$_HOST_1 break . . . See More: https://github.com/upyun/docs/issues/5
  • 35. Lua Custom Cache-Control: Using specific URI rules location ^~ /www/ { if ($query_string ~* "foo=bar") { expires 300s; } } location ^~ /images/ { expires 1h; } location ~* .jpg$ { expires 1d; }
  • 36. Lua Custom SSL: Certificates Load & OCSP stapling server { listen 443 ssl; server_name upyun.com; ssl_certificate upyun.com.pem; ssl_certificate_key upyun.com.key; ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/ssl/private/ca-certs.pem; }
  • 37. Lua Custom Logging: lua-resty-logger-socket log_format combined '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; server { access_log /path/to/access.log combined buffer=4096; . . . } EdgeServer UPYUNLOG bucket:hbimg = {"enable":true,"ratio":0.1} logger.log(cjson.encode(log_msg_table) .. "n")
  • 38. UPYUN LOG Platform: HAProxy + Heka + Kafka + Elasticsearch + Kibana
  • 42. Lua CDN Lua WAF Lua SSL Lua API
  • 44. Thanks Nginx ngx_lua agentzh Lua blog.cloudflare.com Openresty LuaJIT Github Maxim Dounin Igor Sysoev chaoslawful https://groups.google.com/forum/#!forum/OpenResty Ansible Michael Pall Open source …
  • 45. Q & A