問題背景和現象
公司任務管理使用的是開源的redmine,以前是單機部署(bitnami_redmine),后來由于項目數量、人員數量和任務數量的增加,卡頓問題比較明顯,于是改造為基于k8s的分布式集群部署(Nginx+puma)。
改造后有個現象是,wiki標題中,如果包含引號特殊字符,在打開頁面時,redmine后臺將返回400。
wiki標題如下:
400顯示效果如下:
處理辦法
方案1:替換數據庫中的引號,替換為空
- 首先查出wiki標題,使用update進行修改。
這里僅為試驗,沒有使用MySQL的replace函數進行全局替換
- 之后去掉引用頁wiki_content的引號
- 通過瀏覽器測試訪問正常
小結:
此方案雖然可以通過update … replace … 進行全部替換wikis title字段,但由于redmine wiki頁面可以在項目內進行引用(issue和其他wiki均可引用),引用時內容較多,無法區分引號是否需要被替換,所以無法對wiki_contents以及journal_details表進行全局替換
方案2:找到參數丟失的根源
思路大致是先通過對比縮小范圍,找到400是哪個節點返回的,之后再進一步分析具體原因。
- 1.首先看下nginx端是否正常接收到了參數,中文及特殊字符會進行urlencode,我們直接用轉碼后的結果在kibana中進行搜索
url.original : *%E4%B8%BB%E6%8E%A7%E6%9C%BA%E6%8A%A5%E9%94%99*
可以看到參數到達nginx端時,還是對的,這很有可能是nginx再向后端svc傳遞時丟失了參數導致。
我們可以通過curl跳過nginx,直接測試svc地址能否正常返回。
- 2.先復制可以響應200的請求
- 3.在服務器上跳過nginx直接訪問svc地址,url中增加引號字符(%22),同時數據響應狀態碼-w %{http_code}
從上面的測試可以看出,跳過nginx轉發,可以拿到正常的響應結果。
注:簡單說明下,此前置nginx由于各類轉發問題(比如cdn回源、oss圖片轉發等)沒有使用nginx-ingress-controller暴露k8s集群中的服務,使用的是集群外置nginx。
- 4.返回400的請求,在rails日志中并沒有看到處理過程
- 5.rails使用的是puma發布,查看puma日志,可以看到轉換異常的錯誤
到這里基本可以定位到問題節點,處于nginx—>puma時,發送的http請求不被puma認可。
而curl發出的請求,是能被puma認可的。
那么區別到底在哪里?nginx發出的請求為何有差異?
查看nginx error日志沒有線索,問題到這里已經陷入了困境,難道只能抓包分析下?
- 6.服務器上使用tcpdump抓包,期間觸發兩次請求,一次curl svc地址(響應200的),一次curl nginx地址(響應400的)tcpdump -i cnio host 10.10.2.187 -w /tmp/1.pcap
- 7.從服務器傳到本地用wireshark分析
從上面的對比可知,nginx收到瀏覽器發送的請求后,發現有urlencode后的字符%22,會進行解碼后將引號傳遞到后端。
為何其他中文字符沒有解碼,唯獨解碼引號這一個字符?要弄清楚這個問題,需要結合nginx源碼看一下。
- 8.如何避免這個問題?
正常情況下,uri的轉義操作在瀏覽器端已經完成,所以nginx側的$request_uri肯定是encode后的正確狀態,這一點在文章開頭中Kibana的access日志中可以看到。我們可以利用這個參數,對location進行特殊處理
修改前的配置如下
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://10.89.0.8/$1;
}
修改后的配置如下
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
if ($request_uri ~* ^/(.*)$) {
proxy_pass http://10.89.0.8/$1;
}
}
寫在后面
nginx內置變量很多,配合location rewrite正則可以滿足多種轉發場景。
有其他解決思路的朋友可以留言喲~。