核心內容:Docker重構Nginx鏡像,融入Lua、Redis功能
文章目錄
- 前言
- 一、準備工作
- 1、說明
- 2、下載模塊
- 3、Nginx配置文件
- 3、Dockerfile配置文件
- 3、準備工作全部結束
- 二、構建鏡像
- 三、基于鏡像創建容器
- 三、lua腳本的redis功能使用
- 總結
前言
???? ???? 哈嘍,各位it同學們好,今天內容主要是:去重構nginx鏡像,融入lua、Redis功能,能防范CC攻擊,搭建合理有效的WAF 。我知道可能有不少人說OpenResty 已經做了這個事情,他也能去運行lua腳本防范CC攻擊,除此之外還有一些流量監測、流量黑洞、蜜罐技術等等,但是今天我沒有借用這些方式,就是單純的想去加強一下nginx,讓他去融入這些lua功能,然后我自己去寫代碼,進行流量的顆粒度控制。 我在網上也看了不少的文章去增強Nginx,但是好多都不全,而且復刻不成功,今天的話,我就記錄一下我的做法。在開始之前需要強調一下,生成的鏡像他本身還是Nginx,只是給他增強融入lua!!!
提示:以下是本篇文章正文內容,下面案例可供參考。以下教程,本人在4個服務器上面完美復刻,都是經過測試的,跟著我的筆記,你也可以的。
一、準備工作
???? ???? 準備工作其實就是下載東西,還有準備一些初始配置文件。
1、說明
???? ???? Nginx 執行 Lua 腳本的核心依賴是:
???? ???? - LuaJIT
???? ???? - ngx_devel_kit
???? ???? - lua-nginx-module
???? ???? 這3個非常重要!!!殘缺、版本不一致都會使重構鏡像失敗!很多重構Nginx鏡像都是失敗在版本不一致的問題!下載模塊里面的版本,都是個人搜集資料和多次嘗試之后,搭配出來最優解的版本!這些版本在一起不會出錯!
???? ???? 準備的東西一共是10個,8個下載文件、1個nginx配置文件、1個Dockerfile。
2、下載模塊
???? ???? 其實很多的教程,他都是要讓你去在構筑鏡像中去下載的,少的文件,沒有問題,但是如果非常多的話,而且訪問的鏈接、版本不一致的話,就會出錯誤,好多下載的東西都是國外的,很慢的。所以為了避免這些問題,這里我是全部都下載好,然后去放在Dockerfile同級的目錄下。
???? ???? 下面所有的鏈接截止(2025年.5月.6日時)全部能正常下載!!! 下載好之后就可以去和dockerfile放在同一個目錄,而且要注意文件名字!!!鏈接有些是在國外,網絡延遲大,可能需要多試一試,才能下載!
下面是下載的8個鏈接:
下載的OpenSSL模塊,1.1.1u版本 :
https://www.openssl.org/source/openssl-1.1.1u.tar.gz
下載的ngx_devel_kit模塊,v0.3.0版本 :
https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
下載的lua-nginx-module模塊,v0.10.21版本 :
https://github.com/openresty/lua-nginx-module/archive/refs/tags/v0.10.21.tar.gz
下載的nginx源碼:
Nginx 1.20.2 是非常穩定的 LTS(長期支持)版本
nginx 1.20.2版本 http://nginx.org/download/nginx-1.20.2.tar.gz
下載luajit2的源碼,luajit2版本:
git clone https://github.com/openresty/luajit2.git
下載lua-resty-core模塊:
手動去如下地址下載源代碼,下載之后將lua-resty-core-v0.1.17改為:lua-resty-core
v0.1.17版本 https://github.com/openresty/lua-resty-core/releases/tag/v0.1.17
下載lua-resty-lrucache模塊:
git clone https://github.com/openresty/lua-resty-lrucache.git
下載luarocks-3.9.2的命令模塊,版本3.9.2 :
https://luarocks.org/releases/luarocks-3.9.2.tar.gz
鏈接有些是在國外,網絡延遲大,可能需要多試一試,才能下載!!!
鏈接有些是在國外,網絡延遲大,可能需要多試一試,才能下載!!!
鏈接有些是在國外,網絡延遲大,可能需要多試一試,才能下載!!!
3、Nginx配置文件
???? ???? 這個是要用到的默認Nginx配置文件!
nginx.conf 如下:
worker_processes 1;events {worker_connections 1024;
}http {################################# 下面是核心內容,必須包含,否則會報各種錯!# Nginx 的日志直接輸出到標準輸出,方便 docker logs 查看,同時也要注意-v掛載的日志路徑問題!!access_log /var/log/nginx/access.log;error_log /var/log/nginx/error.log warn;# 顯式添加 lua_package_path 路徑lua_package_path "/opt/luarocks-3.9.2/lua_modules/share/lua/5.1/?.lua;;";# 確保 resty.core 啟動時被加載,避免運行時缺模塊init_by_lua_block {require "resty.core"}#################################include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;#gzip on;server {listen 80;server_name localhost;location / {root /usr/share/nginx/html; #必須修改為:/usr/share/nginx/htmlindex index.html index.htm;}}
}
這個nginx.conf我是進行精簡過的!!!核心的內容一定不可少!構筑鏡像的過程中需要這些!!!同時掛載也需要!
3、Dockerfile配置文件
???? ???? 所有點路徑問題、報錯問題,都已經解決了,可以直接抄!而且也可以作為通用模板!下面代碼我都加上注釋了!
# 指定基礎鏡像
FROM centos:7# 替換 yum 源為阿里云鏡像,解決 DNS 和網絡問題
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo# 安裝Nginx和 LuaJIT 所需的依賴
RUN yum update -y && yum install -y \gcc make pcre-devel zlib-devel openssl-devel wget curl unzip git# 編譯和安裝 LuaJIT
# 編譯后的 LuaJIT 會被安裝到 Docker 容器的文件系統中,具體路徑為 /usr/local/,這是默認的安裝目錄。
# 系統環境變量會指向 LuaJIT 的庫和頭文件所在的位置,這樣 Nginx 在編譯時能夠找到并鏈接 LuaJIT。
WORKDIR /opt
COPY luajit2 /opt/luajit2
# 進入目錄并編譯安裝
WORKDIR /opt/luajit2
RUN make && make install# 設置環境變量 。這里的文件路徑和上面是自動保持一致的,不需要改!
# 在編譯 Nginx 時用到了 LuaJIT,那么這里就是顯式告訴他 LuaJIT 的頭文件和庫在哪里等等
ENV LUAJIT_LIB=/usr/local/lib
ENV LUAJIT_INC=/usr/local/include/luajit-2.1#下載并解壓 OpenSSL 1.1.1u 源碼 ,準備好 OpenSSL 源碼,為后續 Nginx 編譯鏈接使用。
COPY openssl-1.1.1u.tar.gz /opt/
WORKDIR /opt
RUN tar -zxvf openssl-1.1.1u.tar.gz# lua-nginx-module 的底層依賴模塊,提供宏和功能(如請求上下文、共享內存等)支持 Lua 腳本在 Nginx 中運行。
# 簡單說:沒它,lua-nginx-module 編譯就失敗,運行也不行。
COPY ngx_devel_kit-0.3.0.tar.gz /opt/
WORKDIR /opt
RUN tar -zxvf ngx_devel_kit-0.3.0.tar.gz# 這段代碼的作用下載 Nginx 模塊源碼,為編譯做準備。
# 這兩部分源碼是后續編譯支持 Lua 的 Nginx 所必須的。
COPY nginx-1.20.2.tar.gz /opt/
WORKDIR /opt
RUN tar -zxvf nginx-1.20.2.tar.gz# 這段代碼的作用下載 lua-nginx-module模塊源碼,為編譯做準備。
COPY lua-nginx-module-0.10.21.tar.gz /opt/
WORKDIR /opt
RUN tar -zxvf lua-nginx-module-0.10.21.tar.gz# -------------------------------
# 安裝 Luarocks
COPY luarocks-3.9.2 /opt/luarocks-3.9.2
WORKDIR /opt/luarocks-3.9.2
# 給 configure 文件增加執行權限
RUN chmod +x ./configure
RUN ./configure --with-lua-include=/usr/local/include/luajit-2.1 && make && make install
# 檢查是否安裝成功
RUN luarocks --version
# -------------------------------# -------------------------------
# 安裝 lua依賴的redis模塊
RUN yum install -y gcc make unzip wget readline-devel
RUN yum install -y lua-devel
RUN luarocks install lua-resty-redis
# 檢查是否安裝成功
RUN find /usr/local -name redis.lua
# -------------------------------# 編譯 Nginx
# Nginx 編譯 Nginx + Lua 模塊時,只需要 lua-nginx-module 和 LuaJIT。
# 這部分代碼的作用是編譯 Nginx,使其支持 LuaJIT 和 lua-nginx-module 模塊。手動編譯 Nginx 的過程。
# WORKDIR /opt/nginx-1.24.0:進入 Nginx 源碼目錄。
# ./configure:配置 Nginx 編譯參數:
# --prefix=/etc/nginx:指定自定義編譯好的Nginx安裝目錄。
# --with-cc-opt=... 和 --with-ld-opt=...:讓編譯器正確找到 LuaJIT 的頭文件和庫。
# --add-module=...:添加 lua-nginx-module 模塊,支持 Lua 腳本。
# --with-openssl=/opt/openssl-1.1.1u 告訴Nginx編譯器:使用我指定的源碼重新編譯并集成進 Nginx,這樣能讓 Nginx 支持新版本的 TLS/SSL 加密功能。
# --with-http_ssl_module:啟用 HTTPS 支持。
# --with-http_stub_status_module:啟用 Nginx 狀態監控模塊。
# make && make install:編譯并安裝 Nginx。
WORKDIR /opt/nginx-1.20.2
RUN ./configure --prefix=/etc/nginx \--with-cc-opt="-O2 -Wno-error -I/usr/local/include/luajit-2.1" \--with-ld-opt="-L/usr/local/lib -Wl,-rpath,/usr/local/lib" \--add-module=/opt/lua-nginx-module-0.10.21\--add-module=/opt/ngx_devel_kit-0.3.0\--with-openssl=/opt/openssl-1.1.1u \--with-http_ssl_module \--with-http_stub_status_module \
&& make && make install# 下面兩個 lua-resty-core 和 lua-resty-lrucache 是 運行時 Lua 模塊,不影響Nginx編譯。
# 安裝 lua-resty-core 和 lua-resty-lrucache 模塊(解決 'resty.core' 報錯)WORKDIR /opt
COPY lua-resty-core /opt/lua-resty-core
# 將 lua-resty-core 中的 lib/resty 目錄復制到容器的 /usr/local/share/lua/5.1 目錄下
RUN cp -r /opt/lua-resty-core/lib/resty /usr/local/share/lua/5.1/WORKDIR /opt
COPY lua-resty-lrucache /opt/lua-resty-lrucache
# 將 lua-resty-lrucache 中的 lib/resty 目錄復制到容器的 /usr/local/share/lua/5.1 目錄下
RUN cp -r /opt/lua-resty-lrucache/lib/resty /usr/local/share/lua/5.1/# 復制自定義的 Nginx 配置文件
# 這一步只是將默認的配置文件復制到容器中,它本身并不會執行任何操作,只是保證 Nginx 在容器中啟動時會使用你自定義的配置文件。
# 到時候我們在啟動容器時,通過 -v 參數將宿主機上的配置文件掛載到容器中,這樣 Nginx 就會使用我們自定義的配置文件了。
# 這一步非常重要!!!
COPY nginx.conf /etc/nginx/nginx.conf # 添加配置文件和啟動腳本
# 聲明容器對外暴露 80 端口(HTTP)。
EXPOSE 80
CMD ["/etc/nginx/sbin/nginx", "-g", "daemon off;"]# /etc/nginx/sbin/nginx含義:來啟動 Nginx,注意哦!這里的路徑是編譯后的Nginx的路徑,上面我們已經指定的!!!
#
# daemon off含義:設置容器啟動時運行的命令,讓 Nginx 以前臺模式啟動并保持運行(不進入守護進程),這樣 Docker 容器才不會退出,Nginx 會持續運行。
???? ???? dockerfile文件都是寫好的,直接復制就行了!!!問題都解決了!!!
3、準備工作全部結束
???? ???? OK!到這里基本上所有的東西全部準備齊了!!!一定要確保其名字、文件層級關系如下:
一共10個文件!!! 下面就是重構鏡像!!!
二、構建鏡像
???? ???? 如果之前的構建過程中出現了錯誤并留下了不完整的文件,可能會導致后續構建失敗。可以嘗試清理之前的構建緩存,并重新構建,不會對你的容器、鏡像、數據卷或系統本身造成危害。清理 Docker 緩存的命令:
docker builder prune
???? ???? 我的建議是在重構鏡像前面執行一次,根據我的實操經驗,他會影響構建鏡像!
???? ???? OK!!!正式開始!
???? ???? --no-cache
:禁用緩存
docker build --no-cache -t nginx-lua:1.20.2 .
如下:
???? ???? 我的重構花費300多秒!!!如果他還要去下載東西,估計要更多的時間!!!這個重構,可能會受網絡失敗!你再去執行一下就可以了!!!
三、基于鏡像創建容器
???? ???? 注意!掛載的目錄必須先創建,而且注意權限問題! 還要注意一點!!那就是把之前的dockerfile文件同級目錄下的nginx.conf文件,要去放到/mydata/nginx_lua/conf/nginx.conf目錄下!!必須的!!!不然報錯的!!!
然后執行下面的命令:
docker run --name nginx-lua -p 80:80 \-e TZ=Asia/Shanghai \-v /mydata/nginx_lua/html:/usr/share/nginx/html \-v /mydata/nginx_lua/conf/nginx.conf:/etc/nginx/conf/nginx.conf \-v /mydata/nginx_lua/page:/mydata/nginx_lua/page \-v /mydata/nginx_lua/lua/waf_limit.lua:/mydata/nginx_lua/lua/waf_limit.lua \-v /mydata/nginx_lua/logs:/var/log/nginx \-d nginx-lua:1.20.2
這個時候就容器就創建好了!!!
下面可以去測試一下!!!
#在server模塊里面,隨便找個地方添加下面的代碼location /lua {default_type 'text/plain';content_by_lua 'ngx.say("hello, lua")';}
訪問如下:
到這里只能說明你的功能等等各方面沒有問題的!!!!
OK,下面我們去測試lua的redis模塊!!!
三、lua腳本的redis功能使用
我給我的模塊加上(粒度控制,加在誰的上面,誰起作用,賊好玩):
location /ithe {access_by_lua_file /mydata/nginx_lua/lua/waf_limit.lua;alias /mydata/nginx_lua/page; }
目錄如下:
waf_limit.lua內容如下:
這個腳本主要的作用就是:基于 IP + User-Agent 組合做訪問頻率限制(限流防護),防范 CC 攻擊。直接復制就可以了(要改一下redis的賬號密碼)!!!!
-- 工具函數:獲取真實 IP(考慮了反向代理)
local function get_client_ip()local headers = ngx.req.get_headers()local ip = headers["X-Real-IP"] or headers["X-Forwarded-For"] or ngx.var.remote_addrif ip and type(ip) == "string" then-- 處理多級代理下的多個IP地址(取第一個)local first_ip = ip:match("([^,]+)")return first_ipendreturn "unknown"
end-- 獲取請求相關信息
local client_ip = get_client_ip() -- 訪問者真實 IP
local user_agent = ngx.var.http_user_agent or "" -- User-Agent-- 打印日志(方便調試)
ngx.log(ngx.ERR, "-----------------------------------[WAF] start:-------------------------------- ")
ngx.log(ngx.ERR, "############# [WAF] IP: ", client_ip," #############")
ngx.log(ngx.ERR, "############# [WAF] User-Agent: ", user_agent," #############")
ngx.log(ngx.ERR, "############# [WAF] URI: ", ngx.var.uri," #############")------------------------------------------------------------------------- 連接 Redis(默認連接 127.0.0.1:6379)
local redis = require "resty.redis"
local red = redis:new()-- 嘗試連接遠程 Redis(請替換成你的實際 IP 和端口)
local ok, err = red:connect("192.168.180.129", 6329)
if not ok thenngx.log(ngx.ERR, "[WAF] Redis 連接失敗: ", err)return ngx.exit(500)
elsengx.log(ngx.ERR, "[WAF] Redis 連接成功")
end-- 使用密碼進行 AUTH 驗證(Redis 6.0+ 支持用戶名)
local res, err = red:auth("XXXXXXXXX")
if not res thenngx.log(ngx.ERR, "[WAF] Redis 用戶名密碼認證失敗: ", err)return ngx.exit(500)
elsengx.log(ngx.ERR, "[WAF] Redis 用戶名密碼認證成功")
end-- 選擇指定數據庫(0-15)
local ok, err = red:select(6)
if not ok thenngx.log(ERR, "[WAF] Redis 選擇數據庫失敗: ", err)return ngx.exit(500)
elsengx.log(ngx.ERR, "[WAF] Redis 已選擇數據庫 5")
end------------------------------------------------------------------------- 統計整體訪問次數(無論限流是否觸發)
local total_key = "Visit:total" -- 你也可以用 ip 直接當 key(比如:"total:" .. ip)-- INCR 總訪問量(永久保存,不設置 expire)
local total_count, err = red:incr(total_key)
if not total_count thenngx.log(ngx.ERR, "[WAF] Redis 總訪問計數累加失敗: ", err)-- 不終止請求流程,統計失敗可以忽略
elsengx.log(ngx.ERR, "[WAF] 當前總訪問次數(永久累加): ", total_count)
end------------------------------------------------------------------------- -- 將 client_ip 和 user_agent 結合起來,生成一個唯一標識設備的 keylocal ip = client_ip or "unknown"
local ua = user_agent or "unknown"
local raw = ip .. "|" .. ualocal function to_hex(n)return string.format("%08x", n) -- 8位十六進制,不足補0
end-- 多次哈希疊加
local part1 = to_hex(ngx.crc32_short(raw))
local part2 = to_hex(ngx.crc32_short(raw .. "1"))
local part3 = to_hex(ngx.crc32_short(raw .. "2"))
local part4 = to_hex(ngx.crc32_short(raw .. "3"))
local key = "limit:" .. part1 .. part2 .. part3 .. part4ngx.log(ngx.ERR, "############# [WAF] redis key = ", key,"#############")
-- so:limit:5d8e5c6f5d8e5c6f5d8e5c6f5d8e5c6f------------------------------------------------------------------------- 理解如下:
-- 第一次訪問?
-- └─ 是 → Redis: set(key, 1) + expire(key, 60s)
-- └─ 否 → Redis: get(key) 變數字
-- └─ 超過10?→ 攔截返回429
-- └─ 沒超限 → incr + 放行-- 設置最大允許次數和過期時間(單位:秒)
local max_count = 500 -- 限制訪問次數:比如最多允許訪問 10 次
local expire_time = 60 -- 規定的設計內容:統計 60 秒內的訪問次數(限流時間段)
-- 比如設置的限流max_count = 10,expire_time = 60,規則就是「60 秒內最多只能訪問 10 次」。-- 從 Redis 中讀取你這個 IP + UA + 其他信息生成的 key 當前的訪問次數。
local count, err = red:get(key)if not count thenngx.log(ngx.ERR, "[WAF] 獲取 Redis 計數失敗: ", err)return ngx.exit(500)
endif count == ngx.null thenlocal ok, err = red:set(key, 1)if not ok thenngx.log(ngx.ERR, "[WAF] Redis 設置初始值失敗: ", err)return ngx.exit(500) endlocal ok, err = red:expire(key, expire_time)if not ok thenngx.log(ngx.ERR, "[WAF] Redis 設置過期時間失敗: ", err)return ngx.exit(500)endngx.log(ngx.ERR, "[WAF] 初次訪問,key 初始化完成")else local num = tonumber(count)if num >= max_count thenngx.log(ngx.ERR, "[WAF] 超過訪問限制,直接攔截")--return ngx.exit(429) -- HTTP 429 Too Many Requests--ngx.say('{"code":429, "msg":"請求過于頻繁,請稍后再試!"}')return ngx.exit(429)elselocal new_count, err = red:incr(key)if not new_count thenngx.log(ngx.ERR, "[WAF] Redis 累加失敗: ", err)return ngx.exit(500)endngx.log(ngx.ERR, "[WAF] 當前訪問次數: ", new_count)end
end-- 關閉Redis 連接
red:close()
ngx.log(ngx.ERR, "-----------------------------------[WAF] over:-------------------------------- ")
OKOK!!!重啟nginx,然后訪問/ithe
然后看一下redis:
再看一下nginx的日志:
???? ???? okok!!!到這里就說明你已經成功讓nginx融入lua、redis功能,并且成功運行!!!
總結
???? ???? 看到這里,不點個贊、收藏下,支持一下?(壞笑)