在k8s中收集jvm異常dump檔案到OSS

peachyy發表於2021-11-11

現狀

加引數 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/test.dump 可以實現在jvm發生記憶體錯誤後 會生成dump檔案 方便開發人員分析異常原因。

當執行在k8s中,如果程式發生錯誤 匯出dump檔案後 ,k8s會重啟dokcer容器,上一次崩潰生成的dump檔案就沒有了。如果應用並沒有完全崩潰 此時極其不穩定 最好也能通知到技術人員來處理。這樣不方便我們排查原因 所有寫了一個小工具。大概原理如下

1、 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/test.dump 當發生記憶體錯誤的時候 匯出堆檔案
2、 -XX:OnOutOfMemoryError=./dumpError.sh 當發生記憶體溢位的時候,讓JVM呼叫一個shell指令碼 這個shell指令碼可以做一些資源整理操作 比如kill掉當前程式並重啟

依賴上面2點jvm特性 就能做到把dump檔案收集起來 是通知技術人員也好(比如傳送訂單、簡訊報警等)、然後再把dump檔案上傳到OSS 或者其他的檔案儲存中。 需要值得注意的是-XX:OnOutOfMemoryError=xx.sh 執行的指令碼不能傳指令碼引數,所以儘可能把引數都封裝在另一個指令碼中。

方案實現

基於Go簡單的寫了一個上傳阿里OSS的方法 這裡用其他任何語言都可以的,至於用GO的原因很簡單,有第三方庫可以呼叫、執行的機器上也不用安裝sdk、比較輕量。
大致邏輯如下

jvmdump.go

init獲取程式的輸入引數

func init(){
	fmt.Println("init....")
	flag.StringVar(&env, "env", "test", "test") //用於區分環境 
	flag.StringVar(&ddtoken, "ddtoken", "", "ddtoken") //用於報警用的 釘釘機器人TOKEN
	flag.StringVar(&dumpFile, "dfile", "", "dfile") // dump檔案的地址
	flag.StringVar(&pod, "pod", "", "pod") //k8s中的pod  只是記錄一下 方便排查 
}

main函式邏輯


func main() {
	fmt.Println("start invoke dump...")
	flag.Parse() //解析輸入引數
	fmt.Printf("dumpFile %s ,env %s token %s\n",dumpFile,env,ddtoken)
	exist, err := FileExists(dumpFile) //驗證dump檔案是否存在 只有存在的時候才去處理收集dump檔案邏輯
	if err != nil {
		fmt.Printf("驗證檔案是否存在發生錯誤![%v]\n", err)
		return
	}
	if exist {
		//https://help.aliyun.com/document_detail/88604.html
		var url=uploadOSS(dumpFile) //上傳阿里oss
		fmt.Printf("OSS上傳完成 %s\n", url)
		if enabledd{
		//釘釘群機器人傳送工具 https://github.com/braumye/grobot 
			notifyDD(url) //通知釘釘群機器人
		}
	}else{
		fmt.Printf("dump檔案不存在 %s\n",dumpFile)
	}
}

構建可執行檔案


  set GOOS=linux
  go build -ldflags "-w -s"

測試 驗證go指令碼是否正確

  echo "ffff">/opt/ttt.dump
 ./jvmdump -env test -dfile /opt/ttt.dump

如果能成功上傳 就可以整合到jvm上跑了,不能成功上傳的話 就需要調一下go了。

另外分享一個-XX:OnOutOfMemoryError=./dumpError.sh 參考。

有這個shell的原因是因為 由於jvmOnOutOfMemoryError目前沒有找到可以傳遞指令碼引數的方法。 所有不能呼叫./jvmdump檔案 故包裝一下,把引數都封裝在dempError.sh中 ,把所有生成的dump檔案 字尾命名都設定為.dump,主要是為了方便查詢。放在一個獨立的目錄也是可以的。

dumpError.sh

#!/bin/bash

#迴圈目錄
traverse_dir()
{
    filepath=$1

    for file in `ls -a $filepath`
    do
        if [ -d ${filepath}/$file ]
        then
            if [[ $file != '.' && $file != '..' ]]
            then
                #遞迴
                traverse_dir ${filepath}/$file
            fi
        else
            #呼叫查詢指定字尾檔案
            check_suffix ${filepath}/$file
        fi
    done
	#看需要 可以kill掉程式,避免jvm沒有完全崩潰 k8s不會重啟pod的情況 造成應用假死問題。
}

#查詢指定字尾的檔案 這裡在k8s環境裡一般只會有一個dump檔案,如果可能存在多個的dump檔案檔案的情況 可能需要變更一下邏輯
check_suffix()
{
    file=$1

	#如果找到dump就呼叫go寫的jvmdump指令碼 
    if [ "${file##*.}"x = "dump"x ];then
        lib/jvmdump -e test -dfile $file -pod $HOSTNAME -ddtoken xxx
    fi
}
traverse_dir /opt/logs

完整程式碼參考

https://github.com/peachyy/jvmdump2k8s.git

相關文章