1. API Gateway 佈署 - lyonwang/TechNotes GitHub Wiki

共通設定部分

/usr/local/openresty/nginx/conf/nginx.conf 共通設定

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;

...

}

Nginx Log 檔傳送至 ELK Log Center 設定說明

安裝 Filebeat

Install on CentOS 7
Download and install the public signing key
sudo rpm --import https://packages.elastic.co/GPG-KEY-elasticsearch
Create yum repo
  • /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
設定 /ect/filebeat/filebeat.yml
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"]

API Gateway: 前端、後台、前端 API 服務

Nginx 設定檔內容說明

必須將以下設定檔置於 /ipip 目錄中

  • 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

安裝 IPIP Lua 模組

sudo opm install pintsized/lua-resty-http
sudo opm get linsir/lua-resty-ipip # use root

nginx.conf 中,黑白名單 Lua script、upstreams 以及 server 設定值

以下重點在修改 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;
      }
   } 
...
}

API Gateway: 第三方 API 服務

Nginx 設定檔內容說明

nginx.conf 中,upstreams 以及 server 設定值

以下重點在修改 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!";
        }
    }
...
}
⚠️ **GitHub.com Fallback** ⚠️