技術分享 | Linux 環境下針對程式維度的監控實現

愛可生雲資料庫 發表於 2022-07-13
Linux

作者:莫善

某網際網路公司高階 DBA。

本文來源:原創投稿

*愛可生開源社群出品,原創內容未經授權不得隨意使用,轉載請聯絡小編並註明來源。


一、背景介紹

運維工作中可能會遇到這麼一個痛點,因線上機器基本都是單機多例項,有時候會出現因為某個例項而影響了整個機器的效能。因缺少程式級別的監控,事後想分析是哪個例項跑滿了系統資源往往比較困難。為了解決這一痛點,迫切希望實現程式級別的監控。

程式級別的資源監控,包括但是不限於CPU,記憶體,磁碟IO,網路流量。

二、前期準備

經瞭解,有一個 process_exporter 可以實現程式監控,但是在實際調研及測試發現,該工具有些不足:

process_exporter https://github.com/ncabatoff/...
  • 監控的物件必須預先配置

我們線上單臺機器可能部署有20個例項,要麼是將20個例項的配置放在一個 process_export ,要麼是單個例項一個 process_export ,不管哪種方式部署 process_export 可能都有些麻煩,另外新加一個想監控的物件也需要重新維護一下 process_exporter 。

我希望是新增待監控機器後能自動發現所有活躍的程式。
  • 不能監控程式的網路情況

測試 process_exporter 發現只有 io、記憶體、cpu等使用情況,沒找到網路監控的指標。

我們線上機器很多還是千兆網路卡,監控網路使用情況的需求更大。
  • 額外的需求

我們的環境可能會有一些臨時程式(不是常駐程式)。

三、需求實現

1、監控採集

最開始的思路很簡單,就想著使用一些系統工具直接讀結果進行解析。但是領導覺得讀取它們的採集結果可能稍微重了一點,可能效率不高,達不到小粒度採集,所以想讓我研究一下直接抓取【/proc/pid/】下面執行態的資料,這種方式效率應該是最高的。但是在實際測試過程中發現,想要通過【/proc/pid/】來實現程式監控的方案真是困難重重,以至於後來暫時放棄該方案了,不過還是想簡單聊一下這個的測試歷程。

[root process-exporter]# ls /proc/1
attr       auxv    clear_refs  comm             cpuset  environ  fd      gid_map  limits    map_files  mem        mounts      net  numa_maps  oom_score      pagemap      personality  root   schedstat  setgroups  stack  statm   syscall  timers   wchan
autogroup  cgroup  cmdline     coredump_filter  cwd     exe      fdinfo  io       loginuid  maps       mountinfo  mountstats  ns   oom_adj    oom_score_adj  patch_state  projid_map   sched  sessionid  smaps      stat   status  task     uid_map
[root process-exporter]# 
這個連線比較詳細的介紹了【/proc/pid】下面的檔案/目錄 https://github.com/NanXiao/gn...

(1)CPU狀態抓取

直接翻車,在【/proc/pid/】下面沒找到CPU相關的狀態資料。

有知道的大佬請指導一下。

(2)MEM狀態抓取

記憶體可以通過【/proc/pid/status】檔案進行抓取。

$ grep "VmRSS:" /proc/3948/status 
VmRSS:    19797780 kB
$

(3)io狀態抓取

同理,io也可以通過【/proc/pid/io】檔案進行抓取。

$ grep "bytes" /proc/3948/io
read_bytes: 7808071458816
write_bytes: 8270093250560

(4)網路狀態抓取

這個也直接翻車,在【/proc/pid/】下面倒是找到了跟網路相關的資訊,比如【/proc/pid/net/dev】,還有【/proc/pid/netstat】。

起初以為dev這個檔案是儲存了程式級網路傳輸資料,但是發現這個檔案記錄的網路流量是整個網路卡的,就是說【/proc/pid/net/dev】和【/proc/net/dev】這兩個檔案記錄的網路流量位元組數基本一樣大。具體測試如下:

首先模擬兩個網路傳輸的程式,因為我測試的機器是有NFS,所以直接從NFS拷貝到本地,就模擬了網路傳輸。

$  ps -ef|grep -- "cp -i -r"|grep -v grep
root      66218 111973 12 17:14 pts/1    00:00:11 cp -i -r Backup_For_TiDB/15101/2022-06-20 /work
root      67099 122467 10 17:14 pts/2    00:00:09 cp -i -r Backup_For_TiDB/15001/2022-06-20 /work
程式號分別是 66218 67099

然後通過同時列印兩個pid對應的【/proc/pid/net/dev】檔案及系統的【/proc/net/dev】檔案做對比

$  cat /proc/66218/net/dev /proc/67099/net/dev /proc/net/dev |grep eth0 && sleep 1 && echo "------------------------" && cat /proc/66218/net/dev /proc/67099/net/dev /proc/net/dev|grep eth0
  eth0: 364616462197417 249383778845    0    0    0     0          0         0 77471452119287 170038153309    0    0    0     0       0          0
  eth0: 364616462197417 249383778845    0    0    0     0          0         0 77471452119287 170038153309    0    0    0     0       0          0
  eth0: 364616462197417 249383778845    0    0    0     0          0         0 77471452119287 170038153309    0    0    0     0       0          0
------------------------
  eth0: 364616675318586 249383924598    0    0    0     0          0         0 77471456448161 170038229547    0    0    0     0       0          0
  eth0: 364616675318586 249383924598    0    0    0     0          0         0 77471456449457 170038229571    0    0    0     0       0          0
  eth0: 364616675318586 249383924598    0    0    0     0          0         0 77471456449835 170038229578    0    0    0     0       0          0
$  

可以看到,這三個【/proc/66218/net/dev】【/proc/67099/net/dev】 【/proc/net/dev】的eth0網路卡的流量是一樣的,就是說【/proc/pid/net/dev】實際也是系統的流量開銷,而不是單個程式對應的流量開銷。

【/proc/pid/net/dev】這個不行就懷疑【/proc/pid/net/netstat】這個檔案是我需要的,但是很難受,幾乎看不懂裡面的資訊,找了很多資料才大致弄清楚裡面的資料,最終發現也不是需要的資料。

/proc/pid/net/netstat的詳情可以參考這裡 https://github.com/moooofly/M...

最終還是妥協了,老老實實使用現成的工具進行採集吧。

top free ps iotop iftop等工具

2、資料分析

確定了採集方式後就是資料的分析了,下面準備挨個分析一下。

(1)CPU

下面這幾個是整機的情況

$ lscpu|grep 'NUMA node0 CPU(s)'|awk '{print $NF}'|awk -F'-' '{print $2+1}'  #機器CPU核心數
$ uptime|awk -F'average: ' '{print $2}'|awk -F, '{print int($1)}'            #機器當前負載情況
$ top -b -n 1|grep '%Cpu(s):' |awk '{print int($8)}'  #idle
$ top -b -n 1|grep '%Cpu(s):' |awk '{print int($10)}' # iowait
這部分比較簡單,直接記錄即可。

下面是針對程式抓取CPU使用情況

$ top -b -n 1|grep -P "^[ 0-9]* "|awk 'NF==12 {
    if($9 > 200 || $10 > 10) {
        for (i=1;i<=NF;i++)
            printf $i"@@@";
        print "";
    }
}'  #程式使用CPU百分比,記憶體百分比,僅記錄使用到cpu和記憶體的程式
這部分稍微有點複雜,結果會儲存到top_dic字典。

這個操作的目的是想著記錄程式的cpu記憶體使用情況,但是會發現top的詳情裡面並沒有程式資訊,所以還需要結合ps輔助一下,具體如下:

ps -ef|awk '{printf $2"@@@" ;for(i=8;i<=NF;i++) {printf $i" "}print ""}'
這部分結果會儲存到ps_dic字典。只需要記錄pid和程式詳情即可,所以對ps做了分析後最後的結果就是【[email protected]@@process_info】,最終top_dic和ps_dic通過pid關聯

(2)MEM

下面是整機的情況

$ free |grep '^Mem:'|awk '{print int($2/1024/1024),int($3/1024/1024),int(($2-$3)/1024/1024)}'

下面是針對程式抓取MEM使用情況

#程式使用MEM百分比在cpu部分就已經採集

記憶體這塊是為了方便並沒有使用proc下面的執行態資料,如果從proc下面採集需要遍歷所有pid,感覺比較麻煩,還不如直接通過top採集一次來的方便(還是順便採集)。但是也有一個弊端,最終計算程式使用記憶體大小會多一個操作,即需要根據MEM百分比對其進行計算換成具體位元組數。

(3)磁碟

下面是整機的磁碟使用情況

$ df|grep ' " + part + "'|awk '$2 > 1024 * 1024 * 50 && /^\//{print $1,int($2/1024/1024),int($3/1024/1024),int($4/1024/1024)}'  #磁碟使用情況
需要資料盤的掛載點,如果沒有配置掛載點會記錄整個機器的所有掛載點(大於50GB的掛載點)的使用情況。

下面是針對程式抓取io使用情況

$ iotop -d 1 -k -o -t -P -qq -b -n 1|awk -F' % ' '
    NR>2{
        OFS="@@@";
        split($1,a," ");
        if(a[5] > 10240 || a[7] > 10240 ) {
            print a[1],a[2],a[5]a[6],a[7]a[8],$NF;
        }
    }
    NR<3{
        print $0;
    }'|awk '
    {
        if(NR==1){
            print $1,$2,$6,$13;
        } else if(NR==2) {
            print $1,$2,$5,$11;
        } else {
            print $0;
        }
    }'
這個採集也稍微有點複雜,結果會儲存到iotop_dic字典,通過pid和top_dic和ps_dic這兩個字典關聯。需要注意的是,在實際測試過程中發現,部分程式的詳情非常長,所以為了避免資料冗餘,程式資訊會記錄到單獨的表【tb_monitor_process_info】,並記錄該串的md5值且將md5作為唯一鍵,這樣可以避免空間的浪費。在展示的時候僅需要通過md5值作為關聯條件即可。

我們需要對結果做分析並加工成我們需要的,我覺得有用的就是【時間】【pid】【讀io】【寫io】【程式資訊】,以及對於io訪問量少的程式直接過濾掉。

綜上,程式級別的cpu,記憶體,io使用情況的採集資料上報給server端大概是下面這樣:

    {
        "19991":{
            "cpu":"50.0",
            "mem":"12.5",
            "io_r":"145",
            "io_w":"14012",
            "md5":"2932fb739fbfed7175c196b42021877b",
            "remarks":"/opt/soft/mysql57/bin/mysqld --defaults-file=//work/mysql23736/etc/my23736.cnf"
        },
        "58163":{
            "cpu":"38.9",
            "mem":"13.1",
            "io_r":"16510",
            "io_w":"1245",
            "md5":"c9e1804bcf8a9a2f7c4d5ef6a2ff1b62",
            "remarks":"/opt/soft/mysql57/bin/mysqld --defaults-file=//work/mysql23758/etc/my23758.cnf"
        }
    }

(4)網路

網路的監控有點難受,沒法基於pid做分析,只能通過ip:port分析來回的流量。

下面是整機的網路使用情況

$ iftop -t -n -B -P -s 1 2>/dev/null|grep Total |awk '
    NR < 3 {
        a = $4;
        if ($4 ~ /MB/) {
            a = ($4 ~ /MB/) ? 1024 * int($4) "KB" : $4;
        } else if ($4 ~ /GB/) {
            a = ($4 ~ /GB/) ? 1024 * 1024 * int($4) "KB" : $4;
        }
        a = (a ~ /KB/) ? int(a) : 0
        print $2, a;
    }
    NR == 3 {
        b = $6;
        if ($6 ~ /MB/) {
            b = ($6 ~ /MB/) ? 1024 * int($6) "KB" : $6;
        } else if ($6 ~ /GB/) {
            b = ($6 ~ /GB/) ? 1024 * 1024 * int($6) "KB" : $6;
        }
        b = (b ~ /KB/) ? int(b) : 0
        print $1, b;
    }'

下面是程式級別的網路使用情況

$ iftop -t -n -B -P -s 2 -L 200 2>/dev/null|grep -P '(<=|=>)'|sed 'N;s/\\n/,/g'|awk 'NF==13{
    if($4 ~ /(K|M|G)B/ || $10 ~ /(K|M|G)B/) {
        if(($4 ~ /KB/ && int($4) > 10240) ||
            ($10 ~ /KB/ && int($10) > 10240) ||
            ($4 ~ /MB/ && int($4) > 10240) ||
            ($10 ~ /MB/ && int($10) > 10240) ||
            ($4 ~ /GB/ || $10 ~ /GB/)) {
                print $2,$4,$8,$10
            }
        }
    }'
這部分比較麻煩的是單位換算及計算。這部分會將結果儲存到iftop_dic。

這個採集也稍微有一點複雜,需要對結果做分析並加工成我們所需要的。我認為有用的就是【出ip:port】【出口流量】【回ip:port】【入口流量】。最後程式網路使用情況的採集資料上報給server端大概是下面這樣子。

{
    "net":{
        "speed":"1000",
        "send":"7168",
        "receive":"8192",
        "Total":"16384",
        "time":"2022-06-29 20:16:20",
        "iftop" : {
            "192.168.168.11:55746":[
                {
                    "remote":"192.168.168.13:18059",
                    "out":"7.94KB",
                    "in":"307KB"
                }
            ],
            "192.168.168.11:60090":[
                {
                    "remote":"192.168.168.13:18053",
                    "out":"6.73KB",
                    "in":"307KB"
                }
            ]
        }
    }
}

至此,所有采集項的監控資料都已經拿到了,下面就是將資料入庫了。

3、資料入庫

監控資料分析了以後,就是將其記錄下來,本專案採用 MySQL 來儲存資料,其中不免涉及一些二次分析,以及注意事項,這裡不準備介紹,放在注意事項部分進行介紹。

4、資料展示

完成了資料分析,資料記錄,最後的工作就是將資料展示出來,供運維人員在需要的時候隨時檢視分析,本專案採用 grafana 將資料展示出來,這個部分有些注意事項,這裡也不準備介紹,放在注意事項進行介紹。可以先來看個效果圖:

技術分享 | Linux 環境下針對程式維度的監控實現

四、注意事項

使用 python3 實現程式碼部分,所有注意事項的解決方案也是僅針對 python3 語法來實現的。

1、ssh環境

資料的採集是通過rpc實現,但是 server 端對 client 的管理都是依賴 ssh ,所以必須保證 server 到所有的 client 都能 ssh 免密登入。

2、長連線

跟 MySQL 的通訊,建議使用長連線。尤其是需要監控的機器個數比較多,如果是短連線會頻繁跟 MySQL 進行建立連線釋放連線,存在一定的不必要開銷。

def f_connect_mysql(): #建立連線
    """
    建立連線
    """
    state = 0

    try :

        db = pymysql.connect(monitor_host, monitor_user, monitor_pass, monitor_db, monitor_port, read_timeout = 2, write_timeout = 5) #連線mysql

    except Exception as e :

        f_write_log(log_opt = "ERROR", log = "[ 建立連線失敗 ] [ " + str(e) + " ]", log_file = log_file)

        db = None
        
    return db

def f_test_connection(db):
    """
    測試連線
    """
    try:

        db.ping()

    except:

        f_connect_mysql()

    return db

def f_close_connection(db):
    """
    關閉連線
    """
    try:

        db.close()

    except:

        db = None

    return db
需要注意,如果是多執行緒的話建議每個執行緒維護一個連線,或者新增互斥鎖,這樣可以避免部分異常。

3、做減法

因為我們是基於機器去做的程式監控,難免會出現很多被監控的物件(一臺機器上千個服務也不是不可能),在測試過程中沒有發現這個問題影響很大,但是在實際上線後發現,如果不優化這個部分會導致 metrics 太多,grafana 的渲染很慢,所以對於不必要的採集記錄,可以在採集的時候就過濾掉,能一定程度避免 client 到 server 端的網路開銷,也能減少磁碟空間的開銷,還提升 grafana 的出圖效率。

優化後,在配置檔案提供了配置項,只有當程式使用系統資源滿足閾值才會被採集。

4、超時機制

(1)操作 MySQL 的超時

既為了程式碼的健壯性,也是為了程式的持續、穩定性都建議加上超時引數,可以避免因為一些極端場景導致讀數或者寫數卡住。

(2)採集資料的超時

生產環境的複雜性,什麼都可能發生,即便是一條簡單到不能再簡單的命令也可能出現卡住,所以加上超時機制吧。這裡需要注意,在新增超時機制的時候發現有些問題,具體測試如下:

Python 3.7.4 (default, Sep  3 2019, 19:29:53) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime,subprocess
>>> s_time = datetime.datetime.now()
>>> res = subprocess.run("echo $(sleep 10)|awk '{print $1}'",shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=2)

省略了很多錯誤輸出

subprocess.TimeoutExpired: Command 'echo $(sleep 10)|awk '{print $1}'' timed out after 2 seconds
>>> e_time = datetime.datetime.now();
>>> 
>>> print(s_time)
2022-06-23 13:05:37.886864
>>> print(e_time)
2022-06-23 13:05:48.353889
>>> print(res)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'res' is not defined
>>> 

可以看到【subprocess.run】設定了兩秒超時,但是兩秒後沒有丟擲異常並結束執行,從執行時間看s_time跟e_time相差11秒,但是整個執行結果是異常的(res都沒有結果),就是說並沒有起到預想的超時效果(預想的效果是到超時閾值後終止執行,返回異常)。

如果操作命令是簡單的命令就沒事,比如將【echo $(sleep 10)|awk '{print $1}'】改成【sleep 10】,這個超時機制就正常。

針對這個超時機制可能會出現失效的情況,在程式碼裡面直接用系統命令timeout代替了。

5、返回值

如果操作命令帶管道等複雜命令,返回值可能並不可信。具體測試如下

>>> res = subprocess.run("echoa|awk '{print $1}'",shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=2)
>>> res.returncode
0
>>>
【echoa|awk '{print $1}'】管道左邊的操作是錯誤的,所以整個返回結果應該是非零(預期是這樣),但是這裡返回了0。原因是在管道操作的場景,bash預設情況是僅獲取最後一個管道的執行返回狀態碼,例如【comm1|comm2|comm3】,如果comm1執行成功,comm2執行失敗,但是comm3執行成功,那整個返回狀態就是執行成功。

解決方案如下

>>> res = subprocess.run("set -o pipefail;echoa|awk '{print $1}'",shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=2)
>>> res.returncode
127
>>> 
執行命令前先加一個set -o pipefail,關於set -o的解釋可以參考下面的描述。
-o    If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all  com‐mands in the pipeline exit successfully.  This option is disabled by default.

五、工作原理

技術分享 | Linux 環境下針對程式維度的監控實現

1、server

  • 執行緒1

這個執行緒會做三個事情:

(1)在server重啟的時候會去讀【tb_monitor_version】表,判斷當前版本號跟MySQL記錄的版本號是否一致,如果不一致就會去更新MySQL記錄的版本號。然後將【tb_monitor_host_config】表所有istate=2的節點更新為istate=1。

(2)管理client上線下線,每30s去讀一次【tb_monitor_host_config】表,將需要上線的節點或者需要下線的節點進行維護。istate=1表示需要上線,就會去部署監控指令碼(升級就更新程式碼),並更新為istate=2,istate=0表示需要下線,會去下線該client節點並更新為istate=-1。

(3)管理client狀態,每30s去讀一次【tb_monitor_host_config,tb_monitor_alert_info,tb_monitor_host_info】表(三表關聯),將最近兩分鐘沒有上報的client且最近5min沒有被告警的節點統計出來並告警。

  • 執行緒2

這個執行緒做兩個事:

(1)等待client上報監控資料,然後進行二次分析並寫到MySQL中。

(2)返回當前版本號給client。

2、client

client端會做三個事情

(1)六執行緒並行去採集【機器cpu】【機器記憶體】【機器磁碟】【機器網路】【程式網路】【程式io,程式cpu,程式記憶體】。採集完畢後,主執行緒會進行分析並上報給server端。

(2)在上報過程中如果遇到連續三次server都是異常狀態就會將server異常記錄(避免多個client同時告警)到【tb_monitor_alert_info】表傳送告警。

(3)上報完成後會判斷自己的版本號跟server端返回的版本號是否一致,如果不一致就會退出程式,等待crontab拉起,以此完成升級。

server端完成程式碼更新,在重啟server的時候會將新程式碼同步到各個client。

3、MySQL

MySQL的作用是存版本資訊,client ip配置,監控資料,以及告警狀態等。

4、grafana

grafana的作用是從MySQL讀取監控資料並展示出來。

5、alert

採用企業微信的機器人作為告警通道。

六、使用限制

1、系統環境

(1)作業系統版本及核心。

$ uname -a
Linux 3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
其他版本沒有測試過,不確定是否能用。

(2)系統工具

監控資料採集依賴於作業系統工具,主要依賴如下:

awk,grep,sed,tr,md5sum
top,iftop,iotop
df,free,lscpu,uptime
ip,netstat
rsync,python3
cd,ssh,timeout
server端到client要有免密登入。

2、軟體環境

軟體版本可能存在相容性問題,所以其他版本不確定是否能用,請各自測試除錯。

(1)Python環境

3.7.4

(2)MySQL版本

5.7.26

(3)grafana版本

8.3.1 建議小版本也要一致。 https://dl.grafana.com/enterp...

七、使用介紹

1、部署server

(1)clone專案
mkdir -p /opt/soft/git
cd /opt/soft/git
git clone https://gitee.com/mo-shan/rpc_for_process_monitor.git
依賴Python3環境,建議3.7.4,要求python3在PATH裡面,安裝過程略。
(2)部署server
cp -r /opt/soft/git/rpc_for_process_monitor /opt/soft/rpc_for_monitor  #注意這裡的目錄是有區別的, 主要是希望開發環境跟實際部署的目錄不一樣, 避免失誤
cd /opt/soft/rpc_for_monitor


$ tree -L 2
.
├── conf
│   └── config.ini                  #配置檔案
├── img                             #忽略
│   ├── all-info.png
│   ├── cpu-info.png
│   ├── disk-info.png
│   ├── grafana-data-source-1.png
│   ├── grafana-data-source-2.png
│   ├── grafana-data-source-3.png
│   ├── grafana-data-source-4.png
│   ├── grafana-data-source-5.png
│   ├── grafana-data-source-6.png
│   ├── grafana-data-source-7.png
│   ├── mem-info.png
│   ├── net-info.png
│   └── process-info.png
├── init                            #初始化檔案
│   ├── grafana.json                #grafana配置模板
│   ├── init.sql                    #mysql建表語句
│   └── requirements.txt            #python3依賴的模組
├── lib                             #庫檔案
│   ├── Config.py                   #解析config.ini
│   ├── ConnectMySQL.py             #連線並操作mysql
│   ├── globalVar.py                #全域性變數
│   ├── Public.py                   #公共函式
│   └── __pycache__
├── LICENSE
├── logs                            #日誌目錄
│   └── info.log                    #日誌檔案
├── py37env                         #虛擬環境,要求在/opt/soft/rpc_for_monitor/py37env下才能使用(activate等檔案的路徑寫死了)
│   ├── bin
│   ├── include
│   ├── lib
│   └── pip-selfcheck.json
├── README.md                       #幫助文件
├── rpc.py                          #主程式
├── start_server.sh                 #server端的啟動指令碼
└── state                           #忽略
    └── state.log

11 directories, 28 files
(3)配置server
vim conf/config.ini #根據實際情況進行編輯
如果需要變更專案目錄,需要將【lib/Config.py】檔案的變數【config_file】也改一下。
[global]
version       = 1.1   #版本號, 通過這個變數控制server和client的程式碼,如果server發現這個配置跟表裡儲存的版本不一致就認為程式碼進行了變更,就會將新程式碼傳到client,client如果發現自己的版本和server版本不一樣會進行重啟,以此達到升級效果。
interval_time = 30    #監控採集粒度單位是秒,即30秒一次,這個不是完全精確的30s一次
retention_day = 30    #監控資料保留天數,即30天
log_file = /opt/soft/rpc_for_monitor/logs/info.log #日誌檔案
script_dir = /opt/soft/rpc_for_monitor             #指令碼目錄,不建議變更
mount_part    = /work  #資料盤掛載點, 也可以不配置,置為空,但是不能刪除這個配置項
log_size      = 20     #日誌檔案大小(MB)限制,超過這個值就會刪除歷史日誌

[RULE] 
cpu = 200    #採集的閾值,200表示某個程式使用cpu大於等於200%才會被採集
mem = 10     #採集的閾值,10表示某個程式使用記憶體大於等於10GB才會被採集
io  = 10240  #採集的閾值,10240表示某個程式使用io(讀寫有一個就算)大於等於10MB才會被採集
net = 10240  #採集的閾值,10240表示某個程式使用網路(進出有一個就算)大於等於10MB才會被採集

[CLIENT]
path = xxxx  #預定義一下作業系統的path,因為client會維護一個cront任務,所以避免因為環境變數問題導致指令碼執行報錯,需要定義一下path
python3 = /usr/local/python3 #python3安裝目錄
py3env = /opt/soft/rpc_for_monitor/py37env #python3虛擬環境目錄,工程自帶了一個虛擬環境,可以直接用(前提是指令碼目錄沒有變更)

[MSM]
wx_url  = xxxx   #企業微信報警url,告警功能需要使用者自己修改一下並測試(如果是告警機器人url+key,可以直接配上就能用,本例就是通過企業微信機器人傳送告警)

[Monitor]  #存放監控資料的MySQL的配置
mysql_host      = xxxx
mysql_port      = xxxx
mysql_user      = xxxx
mysql_pass      = xxxx
省略部分不建議變更的配置
所有目錄不建議修改, 要不然需要變更的地方太多,容易出錯。

2、部署 MySQL

安裝MySQL略,建議的版本:5.7
(1)新建必要的賬戶
用MySQL管理員使用者登入並操作。
create user 'monitor_ro'@'192.%' identified by 'pass1'; #密碼請根據實際情況變更

grant select on dbzz_monitor.* to 'monitor_ro'@'192.%';

create user 'monitor_rw'@'192.%' identified by 'pass2';

grant select,insert,update,delete on dbzz_monitor.* to 'monitor_rw'@'192.%';
monitor_ro使用者給grafana使用, monitor_rw使用者是給程式寫入監控資料的(server端寫資料,client上報給server)。所以注意的是,monitor_ro使用者要給grafana機器授權,monitor_rw使用者要給所有監控物件授權,這個目的是用來控制當server失聯了,第一個發現的client就會向表裡寫一條告警記錄並告警,避免其他client重複操作。
(2)初始化MySQL
用MySQL管理員使用者登入並操作。
cd /opt/soft/rpc_for_monitor
mysql < init/init.sql 
所有表放在dbzz_monitor庫下
(dba:3306)@[dbzz_monitor]>show tables;
+----------------------------+
| Tables_in_dbzz_monitor     |
+----------------------------+
| tb_monitor_alert_info      |   # 告警表, 觸發告警就會在裡面寫入一條記錄, 避免同一時間多次告警。
| tb_monitor_disk_info       |   # 磁碟資訊表,多個盤會記錄多條記錄
| tb_monitor_host_config     |   # client配置表,需要採集監控的機器配置到這裡面就行
| tb_monitor_host_info       |   # 系統層面的監控記錄到這裡面
| tb_monitor_port_net_info   |   # 埠級別的網路監控會記錄到這裡面
| tb_monitor_process_info    |   # 這裡面是記錄了程式資訊,全域性的
| tb_monitor_process_io_info |   # 這裡是記錄的程式的io監控資料
| tb_monitor_version         |   # 記錄版本號,及版本號變更時間
+----------------------------+
6 rows in set (0.00 sec)

(dba:3306)@[dbzz_monitor]>
所有表都有詳細的註釋,請看錶的建表註釋。

3、配置client

配置客戶端很簡單,只需要往MySQL表裡面寫入一條記錄。

use dbzz_monitor;
insert into tb_monitor_host_config(rshost,istate) select '192.168.168.11',1;  
#多個機器就寫多條記錄,server端會有後臺執行緒定時掃描tb_monitor_host_config
#如果有待新增的client就會進行部署
#如果需要下線監控節點直接將istate狀態改成0即可
這裡有個限制條件,這個client端已經有python3環境,否則會報錯。

4、部署grafana

安裝略。

grafana版本:8.3.1,建議小版本也要一致。 https://dl.grafana.com/enterp...

這部分涉及到grafana的配置,所有的配置都已經導成json檔案,使用者直接匯入即可。

具體的操作如下。
(1)新建DataSource

新建一個資料來源

技術分享 | Linux 環境下針對程式維度的監控實現
技術分享 | Linux 環境下針對程式維度的監控實現

需要選擇MySQL資料來源

技術分享 | Linux 環境下針對程式維度的監控實現

資料來源的名稱要求寫【dba_process_monitor】,如果跟grafana配置不一致可能會有影響。

技術分享 | Linux 環境下針對程式維度的監控實現

(2)匯入json配置
$ ll init/grafana.json 
-rw-r--r-- 1 root root 47875 Jun 23 14:28 init/grafana.json

技術分享 | Linux 環境下針對程式維度的監控實現

技術分享 | Linux 環境下針對程式維度的監控實現

技術分享 | Linux 環境下針對程式維度的監控實現

本配置是在grafana 8.3.1 版本下生成的。需要注意一下版本,不同版本可能不相容。如果版本不一致匯入會導致出圖失敗,需要使用者自己重新配置grafana,出圖的sql可以參考一下grafana配置檔案的rawSql的配置【grep rawSql init/grafana.json】。

  • 建議1:【Lagend】的配置都改成【as table】,要不然如果指標太多顯示出來的會很亂
  • 建議2:選擇單位的時候對於不希望進行轉換的可以選【Custom unit】屬性
  • 建議3:【Stacking and null value】屬性建議設定為【null as zero】

5、啟動server

將server端的啟動指令碼配置到crontab中,可以起到守護程式的作用。

echo "*/1 * * * * bash /opt/soft/rpc_for_monitor/start_server.sh" >> /var/spool/cron/root
client端不用管,server啟動以後會自動去管理client。

配置完成後,等待一分鐘檢視日誌【/opt/soft/rpc_for_monitor/logs/info.log】,可以看到類似下面的日誌。

[ 2022-06-30 15:13:01 ] [ INFO ] [ V1.1 Listening for '0.0.0.0:9300' ]
[ 2022-06-30 15:13:04 ] [ INFO ] [ 新加監控節點成功 ] [ 192.168.168.11 ]
[ 2022-06-30 15:13:11 ] [ INFO ] [ 監控資料上報成功 ] [ 192.168.168.11 ] 
埠預設是9300,可以通過修改【/opt/soft/rpc_for_monitor/start_server.sh】這個檔案就行變更監聽埠。

6、效果圖

(1)主頁面

技術分享 | Linux 環境下針對程式維度的監控實現

總共有五個ROW,前面四個是機器級別的監控圖,process是程式的監控圖。
(2)CPU頁面

技術分享 | Linux 環境下針對程式維度的監控實現

整個機器的CPU使用情況。
(3)記憶體頁面

技術分享 | Linux 環境下針對程式維度的監控實現

整個機器的記憶體使用情況。
(4)磁碟頁面

技術分享 | Linux 環境下針對程式維度的監控實現

整個機器的磁碟使用情況,如果沒有定義具體的掛載點,會採集所有的掛載點。
(5)網路頁面

技術分享 | Linux 環境下針對程式維度的監控實現

整個機器的網路使用情況。
(6)程式頁面

技術分享 | Linux 環境下針對程式維度的監控實現

會看到具體的程式對系統資源的使用情況。需要注意,因在採集的時候做了過濾,所以監控資料並不一定是連續的,所以建議配置grafana的【null as zero】,這樣展示監控圖的時候是連續的,而不是很多點。另外某個指標可能為空,這也是正常現象。

八、注意事項

  • server,client端一定要有python3環境。
  • 如果有多個server,在啟動server的時候就要指定多個(用逗號隔開),要不然在部署client的時候只會配上單個server,配置多個的好處就是如果第一個當機/異常,client會上報給其他的server。
  • server在執行過程中可以新增client,只需要在【tb_monitor_host_config】表新增istate為1的記錄即可。同理,如果下線的話就更新istate為0即可,執行中的istate為2,下線後的istate為-1。
  • 該工具有告警功能(如果配置),server掛了(client連續三次都連不上server),會由第一個發現的client記錄到MySQL裡面,併傳送告警,如果client掛了,server會發現並告警(超過兩分鐘未上報告警資料)。
  • 如果需要升級程式碼,只需要測試好新程式碼,確認無誤後,更新到server的部署指令碼目錄,然後kill掉server程式即可,等待crontab拉起就行了,client端的程式碼不用人為進行更新。需要注意,新程式碼一定要記得修改配置檔案的版本號,要不然server端不會發現版本不一致,也就不會下發相關任務去更新client的程式碼。
  • 如果需要修改部署目錄請根據實際情況修改【conf/config.ini】【lib/Config.py】,注意這時候自帶的虛擬環境將不能使用了。強烈不建議變更目錄結構或者目錄名。
  • 因考慮到MySQL效能問題及grafana渲染效能問題,所以增加了採集閾值功能,所以部分皮膚的監控資料可能會沒有(該時間段的程式沒有滿足採集閾值的資料)。

九、寫在最後

本文所有內容僅供參考,因各自環境不同,在使用文中程式碼時可能碰上未知的問題。如有線上環境操作需求,請在測試環境充分測試。