1. API Gateway 佈署 - lyonwang/TechNotes GitHub Wiki
worker_processes 2;
worker_cpu_affinity 00000001 00000010;
error_log logs/error.log crit;
pid logs/nginx.pid;
worker_rlimit_nofile 2048;
events
{
use epoll;
worker_connections 2048;
}
http {
include mime.types;
default_type application/octet-stream;
charset utf-8;
server_names_hash_bucket_size 128;
client_header_buffer_size 2k;
large_client_header_buffers 4 4k;
client_max_body_size 8m;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
open_file_cache max=2048 inactive=20s;
open_file_cache_min_uses 1;
open_file_cache_valid 30s;
tcp_nodelay on;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
log_format main '[$time_iso8601] [$server_name] [$request_method] '
'[$server_addr] [$server_port] [$request_uri] [$query_string] [$request_body] [$remote_user] '
'[$remote_addr] [$server_protocol] [$http_user_agent] '
'[$http_cookie] [$http_referer] [$http_host] [$status] '
'[$bytes_sent] [$request_length] [$request_time] [$request_id] '
'[$proxy_add_x_forwarded_for] [$http_accept] '
'[$http_accept_encoding] [$upstream_http_content_length] '
'[$upstream_http_content_type] [$sent_http_content_type] [$upstream_addr]';
access_log logs/access.log main;
...
}
sudo rpm --import https://packages.elastic.co/GPG-KEY-elasticsearch
- /etc/yum.repos.d/elastic.repo
sudo vim /etc/yum.repos.d/elastic.repo
[elastic-6.x]
name=Elastic repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
- make sure version 6.2.3 does exist
sudo yum list filebeat-6.2.3
- install
sudo yum install filebeat-6.2.3 -y
- To configure the Beat to start automatically during boot
sudo chkconfig --add filebeat
filebeat.prospectors:
- type: log
enabled: true
paths:
- /usr/local/openresty/nginx/logs/access.log
fields:
service: nginx
host: Nginx02
category: nginx-access
fields_under_root: true
- type: log
enabled: true
paths:
- /usr/local/openresty/nginx/logs/error.log
fields:
service: nginx
host: Nginx02
category: nginx-error
fields_under_root: true
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
path: ${path.config}/modules.d/*.yml
setup.template.settings:
index.number_of_shards: 3
output.logstash:
hosts: ["192.168.20.54:5044"]
-
mydata4vipday.datx: IP 地區資料庫
-
region_blacklist.csv: IP 地區黑名單 格式:[兩碼地區碼],... 以逗號分開,無空格
例如:
TW,HK,PH,SG,US
- ip_whitelist.csv: IP 白名單 格式: [起始 IP (轉成 long)],[結束 IP (轉成 long)],[說明]
例如:
2886927152,2886927152,LyonNB
- ip_blacklist.csv: IP 黑名單 格式: [起始 IP (轉成 long)],[結束 IP (轉成 long)],[說明]
例如:
2886927152,2886927152,LyonNB
- cors_allow.list: CORS(跨域存取) 允許網域清單 格式: [域名或]* 換行列出
例如:
*
或者
www.my.com
www.your.com
sudo opm install pintsized/lua-resty-http
sudo opm get linsir/lua-resty-ipip # use root
以下重點在修改 upstream 中 server IP 為 QA 機 IP,port 號請保留並保持所有環境一致;server 區段中的 server_name 也必須改成 QA 環境的 domain name
...
http {
...
# Lua Script
lua_package_path "/usr/local/openresty/site/lualib/resty/ipip/?.lua;;";
init_by_lua_block {
local ipip = require "resty.ipip.client"
cjson = require "cjson"
local opts = {
path = '/ipip/mydata4vipday.datx',
token = '97dc010a0793d97bad1a7d7eb98d5ab87ef0f8ee',
timeout = '2000',
}
ipipc = ipip:new(opts)
-- 檢查 IP 白名單: 有在白名單則回傳 true
function checkIpWhiteList(ipaddr, data_path)
local found = false
if not data_path or not ipaddr then
ngx.log(ngx.ERR, data_path)
--ngx.say(data_path)
return false
end
local ip1, ip2, ip3, ip4 = string.match(ipaddr, "(%d+).(%d+).(%d+).(%d+)")
local ip_uint32 = ip1 * 256 ^ 3 + ip2 * 256 ^ 2 + ip3 * 256 + ip4
--ngx.say(ip_uint32)
local file, err = io.open(data_path, "r")
if file == nil then
ngx.log(ngx.ERR, data_path)
--ngx.say(string.format("file is nill => %s", data_path))
return false
else
for line in file:lines() do
local f, t = line:match("(%d+),(%d+),(.*)")
if f ~= nil and t ~= nil and ip_uint32 >= tonumber(f) and ip_uint32 <= tonumber(t) then
ngx.log(ngx.NOTICE, ipaddr .. " is in IP White List.")
found = true
break
end
end
file:close()
end
return found
end
-- IP 黑名單: 有在黑名單則回傳 true
function checkIPBlackList(ipaddr)
local found = false
if not ipaddr then
ngx.log(ngx.ERR, ipaddr)
return false
end
local file, err = io.open("/ipip/ip_blacklist.csv", "r")
if file == nil then
ngx.log(ngx.ERR, "IP Balck list file:/ipip/ip_blacklist.csv does not exist or read fail.")
return false
else
local ip1, ip2, ip3, ip4 = string.match(ipaddr, "(%d+).(%d+).(%d+).(%d+)")
local ip_uint32 = ip1 * 256 ^ 3 + ip2 * 256 ^ 2 + ip3 * 256 + ip4
for line in file:lines() do
local f, t = line:match("(%d+),(%d+),(.*)")
if f ~= nil and t ~= nil and ip_uint32 >= tonumber(f) and ip_uint32 <= tonumber(t) then
ngx.log(ngx.NOTICE, ipaddr .. " is in IP Black List.")
found = true
break
end
end
file:close()
end
return found
end
-- IP 地區資料庫黑名單: 有在黑名單則回傳 true
function checkRegionBlackList(ipaddr)
local ipipc = ipipc
local cjson = cjson
local iso_2 = ''
-- 查詢 ipip 資料庫
local res, err = ipipc:query_file(ipaddr)
if res == nil then
ngx.log(ngx.ERR, "ipipc:query_file("..ipaddr..") fail")
return false, "ipipc:query_file("..ipaddr..") fail";
else
iso_2 = res["iso_2"]
ngx.log(ngx.INFO, cjson.encode(res)..string.format("iso_2:%s", iso_2))
end
-- 檢查是否為私有 IP
--local isPrivIP = is_ip_private(ipaddr)
--if isPrivIP == true then
-- ngx.log(ngx.NOTICE, ipaddr .. " is in Private IP.")
-- return true, cRes
--end
-- 取得地區黑名單
local blist = {}
local blist_file, err = io.open("/ipip/region_blacklist.csv", "r")
if blist_file == nil then
ngx.log(ngx.ERR, "/ipip/region_blacklist.csv")
else
local blist_str = blist_file:read("*all")
--ngx.log(ngx.NOTICE, blist_str)
blist = string.split(blist_str, ",")
ngx.log(ngx.NOTICE, cjson.encode(blist))
blist_file:close()
end
-- 檢查黑名單
-- Blacklist: nation code(TW, HK, US, PH, SG)
for key, val in ipairs(blist) do
local value = val:gsub("%s+", "")
if (iso_2 ~= nil and string.len(iso_2) ~= 0) and iso_2 == value then
ngx.log(ngx.NOTICE, ipaddr .. " is in Regin Black List.")
return true, res
end
end
return false, res
end
function is_ip_private(ipaddr)
local pri_addrs = {
{ 167772160, 184549375 }, -- 10.0.0.0 ~ 10.255.255.255 : single class A network
{ 2886729728, 2887778303 }, -- 172.16.0.0 ~ 172.31.255.255 : 16 contiguous class B network
{ 3232235520, 3232301055 }, -- 192.168.0.0 ~ 192.168.255.255 : 256 contiguous class C network
{ 2851995648, 2852061183 }, -- 169.254.0.0 ~ 169.254.255.255 : Link-local address also refered to as Automatic Private IP Addressing
{ 2130706432, 2147483647 } -- 127.0.0.0 ~ 127.255.255.255 : localhost
}
local o1,o2,o3,o4 = ipaddr:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" )
local num = 2^24*o1 + 2^16*o2 + 2^8*o3 + o4 -- ip to long
for k,v in pairs(pri_addrs) do
if num >= v[1] and num <=v[2] then
return true
end
end
return false
end
string.split = function(s, p)
local rt= {}
string.gsub(s, "[^"..p.."]+", function(w) table.insert(rt, w) end )
return rt
end
function getClientIP(remoteIP, realIP, xForwardedFor)
local retIP = remoteIP
if xForwardedFor then
local iplist = string.split(xForwardedFor, ", ")
if iplist[1] then
retIP = iplist[1]
end
end
if realIP then
retIP = realIP
end
return retIP
end
-- 跨來源資源共享 Cross-Origin Resource Sharing (CORS) 白名單
function processCORS()
varyHeaders = { "Origin",
"Access-Control-Request-Method",
"Access-Control-Request-Headers" }
allowMethods = { "GET",
"POST",
"PUT",
"DELETE",
"OPTIONS" }
allowHeaders = { "Accept",
"Authorization",
"Cache-Control",
"Content-Type",
"DNT",
"If-Modified-Since",
"Keep-Alive",
"Origin",
"User-Agent",
"X-Requested-With",
"X-Forwarded-For",
"X-REQUEST-ID",
"X-REQUEST-START-UTCTIME",
"AuthToken" }
-- Response headers for CORS: Vary 表示要求客戶端要傳送給 Server 的 CORS Request headers
ngx.header["Vary"] = table.concat(varyHeaders, ",")
if (ngx.var.http_origin) then
if (isInCORSList(ngx.var.http_origin)) then
ngx.header["Access-Control-Allow-Origin"] = ngx.var.http_origin
ngx.header["Access-Control-Allow-Credentials"] = "true"
ngx.header["Access-Control-Allow-Methods"] = table.concat(allowMethods, ",")
ngx.header["Access-Control-Allow-Headers"] = table.concat(allowHeaders, ",")
if(ngx.var.request_method == "OPTIONS") then
-- Tell client that this pre-flight info is valid for 20 days
ngx.header["Access-Control-Max-Age"] = 1728000
ngx.header["Content-Type"] = "text/plain charset=UTF-8"
ngx.header["Content-Length"] = 0;
ngx.log(ngx.INFO, "OPTIONS pass")
ngx.exit(204)
end
end
end
return true
end
function trim(s)
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end
function string.startwith(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
function isInCORSList(origin)
local cors_list = {}
local cors_list_file, err = io.open("/ipip/cors_allow.list", "r")
if cors_list_file == nil then
ngx.log(ngx.ERR, "/ipip/cors_allow.list")
else
for line in cors_list_file:lines() do
cors_list[table.getn(cors_list) + 1] = trim(line)
end
cors_list_file:close()
end
for key, value in ipairs(cors_list) do
if value == "*" or origin:startwith("http://"..value) then
return true
end
end
ngx.log(ngx.NOTICE, origin .. " is not in CORS Allow List.")
return false
end
}
# upstreams
upstream front-end {
server 192.168.21.21:4322;
}
upstream back-end {
server 192.168.21.21:5000;
}
upstream limit-page {
server 192.168.21.21:9474;
}
upstream api-doc {
server 192.168.21.31:4333;
}
upstream member-service {
server 192.168.21.31:54871;
}
upstream billing-service {
server 192.168.21.31:54872;
}
upstream game-service {
server 192.168.21.31:54873;
}
upstream mail-service {
server 192.168.21.31:54879;
}
upstream sms-service {
server 192.168.21.31:54878;
}
upstream payment-service {
server 192.168.21.31:54876;
}
upstream log-service {
server 192.168.21.31:54877;
}
upstream brand-service {
server 192.168.21.31:54880;
}
# Server routings
# 需要將客戶一律導向 https 時 uncomment 這段設定值
#server {
# listen 80 default_server;
# listen [::]:80 default_server;
# # 導向至 HTTPS
# rewrite ^(.*) https://$host$1 permanent;
#}
# Backend
server {
listen 80;
listen 443 ssl;
server_name backend-dev.gb.local;
ssl_certificate /usr/local/openresty/nginx/ssl/nginx.crt;
ssl_certificate_key /usr/local/openresty/nginx/ssl/nginx.key;
location / {
proxy_pass http://back-end;
}
}
# Frontend
server {
listen 80;
listen 443 ssl;
ssl_certificate /usr/local/openresty/nginx/ssl/web-dev-fin.crt;
ssl_certificate_key /usr/local/openresty/nginx/ssl/web-dev-fin.key;
server_name web-dev.gb.local www.my0525.com;
# For Testing
location /testip {
set $clientIP $remote_addr;
set $testip "";
set $result "";
default_type 'text/html';
access_by_lua_block {
local args, err = ngx.req.get_uri_args()
ngx.var.testip = getClientIP(ngx.var.remote_addr, ngx.var.http_x_real_ip, ngx.var.http_x_forwarded_for)
if err ~= "truncated" then
for key, val in pairs(args) do
if key == "ip" then
ngx.var.testip = val
end
end
end
res, err = ipipc:query_file(ngx.var.testip)
ngx.var.result = cjson.encode(res)
}
echo "<!DOCTYPE html><html lang=\"en\"><head><title>Your IP</title><meta charset=\"utf-8\"></head><body>client IP: $clientIP<br/> X-Forwarded-For: $http_x_forwarded_for<br/>remote address: $remote_addr<br/> test IP: $testip <br/> IPIP Result: $result<body></html>";
#echo $result;
}
location / {
set $clientIP $remote_addr;
set $up_frontend "http://limit-page";
access_by_lua_block {
ngx.var.clientIP = getClientIP(ngx.var.remote_addr, ngx.var.http_x_real_ip, ngx.var.http_x_forwarded_for)
local is_pass = true
local is_white = checkIpWhiteList(ngx.var.clientIP, "/ipip/ip_whitelist.csv")
if not is_white then
local is_ip_in_balck_list = checkIPBlackList(ngx.var.clientIP)
if is_ip_in_balck_list == true then
ngx.log(ngx.INFO, "IP in IP Black List.")
is_pass = false
else
local is_black = checkRegionBlackList(ngx.var.clientIP)
if is_black == true then
ngx.log(ngx.INFO, "IP in Region Black List.")
is_pass = false
else
local is_private_ip = is_ip_private(ngx.var.clientIP)
if is_private_ip == true then
ngx.log(ngx.INFO, "Origin IP is Private IP.")
is_pass = false
end
end
end
else
ngx.log(ngx.INFO, "IP in IP White List.")
end
if is_pass == true then
ngx.var.up_frontend = "http://front-end"
end
}
proxy_pass $up_frontend;
}
}
# API Services
server {
listen 80;
listen 443 ssl;
ssl_certificate /usr/local/openresty/nginx/ssl/service-fin.crt;
ssl_certificate_key /usr/local/openresty/nginx/ssl/service-fin.key;
server_name service.gb.local;
# API Documents
location / {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
#rewrite /api-doc/(.*) /api-doc/$1 break;
proxy_pass http://api-doc;
}
location ~* /doc/member {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://member-service;
}
location ~* /doc/game {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://game-service;
}
location ~* /doc/mail {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://mail-service;
}
location ~* /doc/sms {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://sms-service;
}
location ~* /doc/payment {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://payment-service;
}
location ~* /doc/billing {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://billing-service;
}
location ~* /doc/brand {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://brand-service;
}
# Member Service
location ~* /member {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://member-service;
}
# Billing Service
location ~* /billing {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://billing-service;
}
# Game Service
location ~* /game {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://game-service;
}
# Mail Service
location ~* /mail {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://mail-service;
}
# SMS Service
location ~* /sms {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://sms-service;
}
# Payment Service
location ~* /payment {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://payment-service;
}
# Log Service
location ~* /log {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://log-service;
}
# Brand Service
location ~* /brand {
access_by_lua_block {
local pass = processCORS()
ngx.log(ngx.NOTICE, "CORS pass: "..(pass and "true" or "false"))
}
proxy_pass http://brand-service;
}
}
...
}
以下重點在修改 upstream 中 server IP,port 號請保留並保持所有環境一致
http {
...
upstream game-ag {
server 192.168.21.31:44571;
}
# Game-AG
server {
listen 80;
server_name api-dev.ips8sz.com;
location ~* /game/ag {
rewrite (?i)^/game/ag/(.*) /$1 break;
proxy_pass http://game-ag;
#echo "Got it!";
}
}
...
}