"inspect": Apache APISIX Lua डायनामिक डिबगिंग प्लगइन
JinHua Luo
January 29, 2023
हमें Lua डायनामिक डिबगिंग प्लगइन की आवश्यकता क्यों है?
Apache APISIX में बड़ी मात्रा में Lua कोड शामिल है। क्या कोई तरीका है जिससे हम रनटाइम पर कोड में वेरिएबल्स के मानों का निरीक्षण कर सकें, सोर्स कोड को बदले बिना?
Lua सोर्स कोड को डिबगिंग के लिए संशोधित करने के कई नुकसान हैं:
- प्रोडक्शन वातावरण में सोर्स कोड को संशोधित करने की अनुमति नहीं होती है और अक्सर यह संभव नहीं होता है
- सोर्स कोड को संशोधित करने से रीलोडिंग की आवश्यकता होती है, जो व्यावसायिक प्रक्रियाओं के कामकाज को बाधित करता है
- कंटेनराइज्ड वातावरण में सोर्स कोड को संशोधित करना चुनौतीपूर्ण हो सकता है
- अस्थायी रूप से उत्पन्न कोड को रोलबैक करने के लिए छोड़ दिया जा सकता है, जिससे रखरखाव की समस्याएं उत्पन्न होती हैं
हमें आमतौर पर वेरिएबल्स के मानों का निरीक्षण करने की आवश्यकता होती है, न केवल जब फंक्शन शुरू या समाप्त होते हैं, बल्कि जब विशिष्ट शर्तें पूरी होती हैं, जैसे कि जब एक लूप कुछ पुनरावृत्तियों तक पहुंचता है या जब एक विशिष्ट शर्त सत्य होती है। इसके अलावा, केवल वेरिएबल्स के मानों को प्रिंट करना हमेशा पर्याप्त नहीं होता है; यह भी आवश्यक हो सकता है कि संबंधित जानकारी को बाहरी सिस्टम पर भेजा जाए। इसके अलावा, इस प्रक्रिया को डायनामिक कैसे बनाया जाए, और क्या इसे प्रोग्राम के प्रदर्शन पर नकारात्मक प्रभाव डाले बिना किया जा सकता है?
Lua डायनामिक डिबगिंग प्लगइन, inspect, आपको उपरोक्त आवश्यकताओं को पूरा करने में मदद करता है।
- कस्टमाइज़ेबल ब्रेकपॉइंट हैंडलिंग
- डायनामिक ब्रेकपॉइंट सेटिंग
- कई ब्रेकपॉइंट सेट किए जा सकते हैं
- ब्रेकपॉइंट को केवल एक बार ट्रिगर करने के लिए सेट किया जा सकता है
- प्रदर्शन पर प्रभाव को नियंत्रित किया जा सकता है और इसे एक विशिष्ट स्कोप में रखा जा सकता है
प्लगइन का कार्य सिद्धांत
यह Lua के Debug API फंक्शन का पूरी तरह से उपयोग करके अपनी सुविधाओं को लागू करता है। इंटरप्रेटर मोड के दौरान, प्रत्येक निष्पादित बाइटकोड को एक विशिष्ट फाइल और लाइन नंबर से मैप किया जा सकता है। ब्रेकपॉइंट सेट करने के लिए, हमें केवल यह जांचना होता है कि लाइन नंबर अपेक्षित मान के बराबर है या नहीं और हमारे द्वारा पहले से परिभाषित ब्रेकपॉइंट फंक्शन को निष्पादित करना होता है। यह हमें संबंधित लाइन के संदर्भ जानकारी, जैसे कि upvalue, लोकल वेरिएबल्स, और कुछ मेटाडेटा, जैसे कि स्टैक, को प्रोसेस करने की अनुमति देता है।
APISIX Lua के JIT इम्प्लीमेंटेशन: LuaJIT का उपयोग करता है, जहां कई हॉट कोड पाथ्स को मशीन कोड में कंपाइल किया जाता है। हालांकि, ये Debug API से प्रभावित नहीं होते हैं, इसलिए हमें ब्रेकपॉइंट चालू करने से पहले JIT कैश को साफ करने की आवश्यकता होती है। महत्वपूर्ण बात यह है कि हम केवल एक विशिष्ट Lua फंक्शन के JIT कैश को साफ कर सकते हैं, जिससे ग्लोबल प्रदर्शन पर प्रभाव कम होता है। जब एक प्रोग्राम चलता है, तो कई JIT-कंपाइल्ड कोड ब्लॉक LuaJIT में trace नाम से जाने जाते हैं। ये ट्रेस Lua फंक्शन्स से जुड़े होते हैं, और एक Lua फंक्शन में कई ट्रेस शामिल हो सकते हैं, जो फंक्शन के भीतर विभिन्न हॉट पाथ्स को संदर्भित करते हैं।
हम उनके फंक्शन ऑब्जेक्ट्स को निर्दिष्ट कर सकते हैं और उनके JIT कैश को साफ कर सकते हैं, ग्लोबल और मॉड्यूल-लेवल फंक्शन्स के लिए। हालांकि, यदि लाइन नंबर अन्य फंक्शन प्रकारों, जैसे कि अनाम फंक्शन्स, से संबंधित होता है, तो हम ग्लोबल रूप से फंक्शन ऑब्जेक्ट प्राप्त नहीं कर सकते हैं। ऐसे मामलों में, हम केवल सभी JIT कैश को साफ कर सकते हैं। डिबगिंग के दौरान नए ट्रेस उत्पन्न नहीं किए जा सकते हैं, लेकिन मौजूदा अन-साफ किए गए ट्रेस चलते रहते हैं। जब तक हमारे पास पर्याप्त नियंत्रण होता है, प्रोग्राम का प्रदर्शन प्रभावित नहीं होगा, क्योंकि एक ऑनलाइन सिस्टम जो लंबे समय से चल रहा होता है, आमतौर पर नए ट्रेस उत्पन्न नहीं करता है। एक बार डिबगिंग समाप्त हो जाने और सभी ब्रेकपॉइंट्स को रद्द कर दिए जाने के बाद, सिस्टम सामान्य JIT मोड में वापस आ जाएगा, और साफ किया गया JIT कैश फिर से हॉट स्पॉट में प्रवेश करने पर पुनः उत्पन्न हो जाएगा।
इंस्टॉलेशन और कॉन्फ़िगरेशन
यह प्लगइन डिफ़ॉल्ट रूप से सक्षम होता है।
इस प्लगइन को सक्षम करने के लिए conf/confg.yaml को सही ढंग से कॉन्फ़िगर करें:
plugins: ... - inspect plugin_attr: inspect: delay: 3 hooks_file: "/usr/local/apisix/plugin_inspect_hooks.lua"
प्लगइन डिफ़ॉल्ट रूप से हर 3 सेकंड में फाइल '/usr/local/apisix/plugin_inspect_hooks.lua' से ब्रेकपॉइंट परिभाषाएं पढ़ता है। डिबग करने के लिए, आपको केवल इस फाइल को संपादित करने की आवश्यकता है।
हम इस पथ पर एक सॉफ्ट लिंक बनाने की सलाह देते हैं ताकि ब्रेकपॉइंट फाइल के विभिन्न ऐतिहासिक संस्करणों को आसानी से संग्रहीत किया जा सके।
कृपया ध्यान दें कि जब फाइल को संशोधित किया जाता है, तो प्लगइन सभी पिछले ब्रेकपॉइंट्स को साफ कर देगा और ब्रेकपॉइंट फाइल में परिभाषित सभी नए ब्रेकपॉइंट्स को सक्षम कर देगा। ये ब्रेकपॉइंट्स सभी वर्कर प्रक्रियाओं में प्रभावी होंगे।
आमतौर पर, इस फाइल को हटाने की आवश्यकता नहीं होती है, क्योंकि ब्रेकपॉइंट्स को परिभाषित करते समय, आप यह निर्दिष्ट कर सकते हैं कि उन्हें कब रद्द किया जाए।
इस फाइल को हटाने से सभी वर्कर प्रक्रियाओं के लिए सभी ब्रेकपॉइंट्स रद्द हो जाएंगे।
ब्रेकपॉइंट्स के शुरू और बंद होने के लॉग्स 'WARN' लॉग लेवल पर रिकॉर्ड किए जाएंगे।
ब्रेकपॉइंट्स को परिभाषित करें
require("apisix.inspect.dbg").set_hook(file, line, func, filter_func)
file, फाइल का नाम, जो कोई भी स्पष्ट फाइल नाम या पथ हो सकता है।line, फाइल में लाइन नंबर, कृपया ध्यान दें कि ब्रेकपॉइंट्स लाइन नंबरों से निकटता से जुड़े होते हैं, इसलिए यदि कोड बदलता है, तो लाइन नंबर भी बदलना चाहिए।func, उस फंक्शन का नाम जिसका ट्रेस साफ किया जाना चाहिए। यदि यहnilहै, तोluajit vmमें सभी ट्रेस साफ हो जाएंगे।filter_func, एक कस्टम Lua फंक्शन जो ब्रेकपॉइंट को हैंडल करता है- इनपुट पैरामीटर एक
tableहोता है जिसमें निम्नलिखित शामिल होते हैं:finfo:debug.getinfo(level, "nSlf")का रिटर्न वैल्यूuv: upvalues हैश टेबलvals: लोकल वेरिएबल्स हैश टेबल
- यदि फंक्शन का रिटर्न वैल्यू
trueहै, तो ब्रेकपॉइंट स्वचालित रूप से रद्द हो जाएगा। अन्यथा, ब्रेकपॉइंट प्रभावी रहेगा।
- इनपुट पैरामीटर एक
उदाहरण के लिए:
local dbg = require "apisix.inspect.dbg" dbg.set_hook("limit-req.lua", 88, require("apisix.plugins.limit-req").access, function(info) ngx.log(ngx.INFO, debug.traceback("foo traceback", 3)) ngx.log(ngx.INFO, dbg.getname(info.finfo)) ngx.log(ngx.INFO, "conf_key=", info.vals.conf_key) return true end) dbg.set_hook("t/lib/demo.lua", 31, require("t.lib.demo").hot2, function(info) if info.vals.i == 222 then ngx.timer.at(0, function(_, body) local httpc = require("resty.http").new() httpc:request_uri("http://127.0.0.1:9080/upstream1", { method = "POST", body = body, }) end, ngx.var.request_uri .. "," .. info.vals.i) return true end return false end) --- more breakpoints ...
कृपया ध्यान दें कि demo ब्रेकपॉइंट कुछ जानकारी को व्यवस्थित करता है और इसे एक बाहरी सर्वर पर भेजता है। इसके अलावा, उपयोग किया गया resty.http लाइब्रेरी cosocket पर आधारित एक एसिंक्रोनस लाइब्रेरी है।
जब भी OpenResty के एसिंक्रोनस API को कॉल किया जाता है, इसे timer का उपयोग करके विलंब के साथ भेजा जाना चाहिए, क्योंकि ब्रेकपॉइंट्स पर फंक्शन्स को निष्पादित करना सिंक्रोनस और ब्लॉकिंग होता है, यह nginx के मुख्य प्रोग्राम को एसिंक्रोनस प्रोसेसिंग के लिए वापस नहीं लौटाएगा, इसलिए इसे विलंबित करने की आवश्यकता होती है।
उपयोग के मामले
अनुरोध बॉडी की सामग्री के आधार पर रूट्स का निर्धारण
मान लीजिए हमारे पास एक आवश्यकता है: हम एक ऐसा रूट कैसे सेट कर सकते हैं जो केवल POST अनुरोधों को स्वीकार करता है जिनकी अनुरोध बॉडी में स्ट्रिंग APISIX: 666 हो?
रूट कॉन्फ़िगरेशन में, एक vars फील्ड होता है, जिसका उपयोग nginx वेरिएबल्स के मानों की जांच करने के लिए किया जा सकता है ताकि यह निर्धारित किया जा सके कि रूट मेल खाना चाहिए या नहीं। nginx द्वारा प्रदान किया गया $request_body वेरिएबल अनुरोध बॉडी के मान को शामिल करता है, इसलिए हम इस वेरिएबल का उपयोग करके अपनी आवश्यकता को लागू कर सकते हैं।
आइए रूट्स को कॉन्फ़िगर करने का प्रयास करें:
curl http://127.0.0.1:9180/apisix/admin/routes/var_route \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { "uri": "/anything", "methods": ["POST"], "vars": [["request_body", "~~", "APISIX: 666"]], "upstream": { "type": "roundrobin", "nodes": { "httpbin.org": 1 } } }'
फिर हम इसे आज़मा सकते हैं:
curl http://127.0.0.1:9080/anything {"error_msg":"404 Route Not Found"} curl -i http://127.0.0.1:9080/anything -X POST -d 'hello, APISIX: 666.' HTTP/1.1 404 Not Found Date: Thu, 05 Jan 2023 03:53:35 GMT Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Server: APISIX/3.0.0 {"error_msg":"404 Route Not Found"}
अजीब, यह रूट क्यों मेल नहीं खाता है?
हम फिर इस वेरिएबल के लिए nginx में डॉक्यूमेंटेशन को देखेंगे:
वेरिएबल का मान proxy_pass, fastcgi_pass, uwsgi_pass, और scgi_pass डायरेक्टिव्स द्वारा प्रोसेस किए गए लोकेशन्स में उपलब्ध होता है जब अनुरोध बॉडी को मेमोरी बफर में पढ़ा जाता है।
दूसरे शब्दों में, हमें पहले अनुरोध बॉडी को पढ़ने की आवश्यकता होती है इससे पहले कि हम इस वेरिएबल का उपयोग करें।
जब रूट को मेल किया जाता है, तो क्या यह वेरिएबल खाली होगा? हम इसे सत्यापित करने के लिए inspect प्लगइन का उपयोग कर सकते हैं।
हमने रूट मैच करने वाले कोड की लाइन पाई:
apisix/init.lua
... api_ctx.var.request_uri = api_ctx.var.uri .. api_ctx.var.is_args .. (api_ctx.var.args or "") router.router_http.match(api_ctx) local route = api_ctx.matched_route if not route then ...
आइए लाइन 515 में वेरिएबल request_body को सत्यापित करें, जो router.router_http.match(api_ctx) है।
ब्रेकपॉइंट्स सेट करें
फाइल /usr/local/apisix/example_hooks.lua को संपादित करें:
local dbg = require("apisix.inspect.dbg") dbg.set_hook("apisix/init.lua", 515, require("apisix").http_access_phase, function(info) core.log.warn("request_body=", info.vals.api_ctx.var.request_body) return true end)
ब्रेकपॉइंट फाइल पथ पर एक सॉफ्ट लिंक बनाएं:
ln -sf /usr/local/apisix/example_hooks.lua /usr/local/apisix/plugin_inspect_hooks.lua
लॉग्स की जांच करें ताकि यह सुनिश्चित हो सके कि ब्रेकपॉइंट प्रभावी है।
2023/01/05 12:02:43 [warn] 1890559#1890559: *15736 [lua] init.lua:68: setup_hooks(): set hooks: err: true, hooks: ["apisix\/init.lua#515"], context: ngx.timer
रूट्स मैचिंग को फिर से ट्रिगर करें:
curl -i http://127.0.0.1:9080/anything -X POST -d 'hello, APISIX: 666.'
लॉग्स की जांच करें:
2023/01/05 12:02:59 [warn] 1890559#1890559: *16152 [lua] [string "local dbg = require("apisix.inspect.dbg")..."]:39: request_body=nil, client: 127.0.0.1, server: _, request: "POST /anything HTTP/1.1", host: "127.0.0.1:9080"
निश्चित रूप से, request_body खाली है!
समाधान
चूंकि हम जानते हैं कि हमें request_body वेरिएबल का उपयोग करने के लिए अनुरोध बॉडी को पढ़ने की आवश्यकता होती है, इसलिए हम इसे करने के लिए vars का उपयोग नहीं कर सकते हैं। इसके बजाय, हम रूट में filter_func फील्ड का उपयोग करके अपनी आवश्यकता को पूरा कर सकते हैं।
curl http://127.0.0.1:9180/apisix/admin/routes/var_route \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { "uri": "/anything", "methods": ["POST"], "filter_func": "function(_) return require(\"apisix.core\").request.get_body():find(\"APISIX: 666\") end", "upstream": { "type": "roundrobin", "nodes": { "httpbin.org": 1 } } }'
आइए सत्यापित करें:
curl http://127.0.0.1:9080/anything -X POST -d 'hello, APISIX: 666.' { "args": {}, "data": "", "files": {}, "form": { "hello, APISIX: 666.": "" }, "headers": { "Accept": "*/*", "Content-Length": "19", "Content-Type": "application/x-www-form-urlencoded", "Host": "127.0.0.1", "User-Agent": "curl/7.68.0", "X-Amzn-Trace-Id": "Root=1-63b64dbd-0354b6ed19d7e3b67013592e", "X-Forwarded-Host": "127.0.0.1" }, "json": null, "method": "POST", "origin": "127.0.0.1, xxx", "url": "http://127.0.0.1/anything" }
समस्या हल हो गई!
लॉग लेवल द्वारा ब्लॉक किए गए कुछ लॉग्स को प्रिंट करें।
आमतौर पर, हम प्रोडक्शन वातावरण में INFO लेवल लॉग्स को सक्षम नहीं करते हैं, लेकिन कभी-कभी हमें कुछ विस्तृत जानकारी की जांच करने की आवश्यकता होती है। हम यह कैसे कर सकते हैं?
हम आमतौर पर सीधे INFO लेवल सेट नहीं करते हैं और फिर रीलोड नहीं करते हैं, क्योंकि इसके दो नुकसान हैं:
- बहुत अधिक लॉग्स, जो प्रदर्शन को प्रभावित करते हैं और जांच की कठिनाई को बढ़ाते हैं
- रीलोडिंग से स्थायी कनेक्शन टूट जाते हैं, जो ऑनलाइन ट्रैफिक को प्रभावित करते हैं
आमतौर पर, हमें केवल एक विशिष्ट बिंदु के लॉग्स की जांच करने की आवश्यकता होती है; उदाहरण के लिए, हम सभी जानते हैं कि APISIX कॉन्फ़िगरेशन वितरण डेटाबेस के रूप में etcd का उपयोग करता है, तो क्या हम देख सकते हैं कि डेटा प्लेन पर रूट कॉन्फ़िगरेशन कब अपडेट होता है? क्या विशिष्ट डेटा अपडेट किया गया है?
apisix/core/config_etcd.lua
local function sync_data(self) ... log.info("waitdir key: ", self.key, " prev_index: ", self.prev_index + 1) log.info("res: ", json.delay_encode(dir_res, true), ", err: ", err) ... end
इंक्रीमेंटल सिंक्रोनाइज़ेशन के लिए Lua फंक्शन sync_data() है, लेकिन यह etcd watch से इंक्रीमेंटल डेटा को INFO लेवल पर प्रिंट करता है।
तो आइए inspect प्लगइन का उपयोग करके इसे प्रदर्शित करने का प्रयास करें। हम केवल रूट्स रिसोर्सेज के परिवर्तनों को दिखाएंगे।
फाइल /usr/local/apisix/example_hooks.lua को संपादित करें:
local dbg = require("apisix.inspect.dbg") local core = require("apisix.core") dbg.set_hook("apisix/core/config_etcd.lua", 393, nil, function(info) local filter_res = "/routes" if info.vals.self.key:sub(-#filter_res) == filter_res and not info.vals.err then core.log.warn("etcd watch /routes response: ", core.json.encode(info.vals.dir_res, true)) return true end return false end)
इस ब्रेकपॉइंट हैंडलिंग फंक्शन का लॉजिक स्पष्ट रूप से फिल्टरिंग क्षमता को दर्शाता है। यदि watch का key /routes है, और err खाली है, तो यह etcd द्वारा लौटाए गए डेटा को केवल एक बार प्रिंट करेगा, फिर ब्रेकपॉइंट को रद्द कर देगा।
ध्यान दें कि sync_data() एक लोकल फंक्शन है, इसलिए इसे सीधे संदर्भित नहीं किया जा सकता है।
ऐसे मामले में, हम set_hook के तीसरे पैरामीटर को nil सेट कर सकते हैं, जिसका साइड इफेक्ट सभी ट्रेस को साफ करना होता है।
उपरोक्त उदाहरण में, हमने एक सॉफ्ट लिंक बनाया है, इसलिए हमें केवल संपादन के बाद फाइल्स को सहेजने की आवश्यकता है। ब्रेकपॉइंट्स कुछ सेकंड के बाद सक्षम हो जाएंगे; आप लॉग की जांच करके इसकी पुष्टि कर सकते हैं।
लॉग की जांच करके, हमें आवश्यक जानकारी मिलती है, जो WARN लॉग लेवल पर प्रिंट होती है।
यह डेटा प्लेन पर etcd इंक्रीमेंटल डेटा प्राप्त करने का समय भी दिखाता है।
2023/01/05 14:33:10 [warn] 1890562#1890562: *231311 [lua] [string "local dbg = require("apisix.inspect.dbg")..."]:41: etcd watch /routes response: {"headers":{"X-Etcd-Index":"24433"}, "body":{"node":[{"value":{"uri":"\/anything", "plugins":{"request-id":{"header_name":"X-Request-Id","include_in_response":true,"algorithm":"uuid"}}, "create_time":1672898912,"status":1,"priority":0,"update_time":1672900390, "upstream":{"nodes":{"httpbin.org":1},"hash_on":"vars","type":"roundrobin","pass_host":"pass","scheme":"http"}, "id":"reqid"},"key":"\/apisix\/routes\/reqid","modifiedIndex":24433,"createdIndex":24429}]}}, context: ngx.timer
निष्कर्ष
Lua डायनामिक डिबगिंग एक महत्वपूर्ण सहायक फंक्शन है। APISIX के inspect प्लगइन के साथ, हम बहुत सी चीजें कर सकते हैं जैसे:
- समस्याओं का कारण खोजना और पहचानना
- कुछ ब्लॉक किए गए लॉग्स को प्रिंट करना और आवश्यकतानुसार विभिन्न जानकारी प्राप्त करना
- डिबगिंग करके Lua कोड सीखना
अधिक जानकारी के लिए कृपया इन संबंधित डॉक्यूमेंटेशन को पढ़ें।