aoishiの備忘録

備忘録

自宅VPNにAndroidで接続する時のメモ

Androidスマホを機種交換した際に、自宅VPNに接続する設定を忘れてしまってたのでメモとしてまとめておきます。

環境

  • 端末: HUAWEI P10 lite
  • Androidバージョン: 7.0
  • VPNサーバ: EdgeRouter X v1.9.1

自宅VPN

ちょうど一年前に友人の勧めでEdgeRouter X買いました。 これを自宅のVPNサーバとして運用しています。

www.ubnt.com

設定方法は下記を参考にしていて、事前共通鍵方式のL2TP/IPsecで設定しています。

yabe.jp

Androidの設定

EdgeRouterのCLIで下記コマンドを叩くと設定内容が設定コマンド形式で表示されます。

この情報とクライアント側の設定に必要なパラメタと紐付けながら記載しておきます。

$ show configuration commands

VPNネットワークの追加時に必要な情報

項目 設定値
名前 適当な名前を入力
タイプ L2TP/IPSec PSK
サーバアドレス 自宅のIP、DNS名を入力
L2TPセキュリティ保護 未使用(入力しないこと)
IPSec ID 未使用(入力しないこと)
IPSec事前共有鍵 set vpn l2tp remote-access ipsec-settings authentication pre-shared-secret XXXX で設定したXXXXの文字列

VPNネットワーク接続時に必要な情報

項目 設定値
ユーザ名 set vpn l2tp remote-access authentication local-users username XXXX password YYYY で設定したXXXXの文字列
パスワード set vpn l2tp remote-access authentication local-users username XXXX password YYYY で設定したYYYYの文字列

Pythonのsetproctitleモジュールでプロセス名を設定する

Pythonのmultiprocessingモジュールで書かれたマルチプロセスなプログラムで、psコマンドで親・子プロセス名を分かりやすく表示させるために、setproctitleモジュールを使ってプロセス名を設定してみました。

環境

multiprocessingモジュールを使ったマルチプロセスなプログラム

下記は、単にsleepする子プロセスを2つ作成してデーモン化するプログラムです。

試しにp1というプロセスにのみname引数を渡しています。マニュアルによるとプロセスの名前と記載されていますが、psコマンドで確認するとどうなるでしょうか。

17.2. multiprocessing — プロセスベースの並列処理 — Python 3.6.3 ドキュメント

from multiprocessing import Process
import time

def child_process():
    while True:
        time.sleep(1)

def main():

    try:
        p1 = Process(target=child_process, name='child process 1')
        p1.daemon = True
        p1.start()

        p2 = Process(target=child_process)
        p2.daemon = True
        p2.start()

        while True:
            time.sleep(1)

    except:
        p1.terminate()
        p2.terminate()

if __name__ == '__main__':
    main()

下記が、上記プログラムを実行してpsコマンドでプロセス名を確認してみた結果です。

$ python test_setproctitle_1.py
※ 下記の別ターミナルでのプロセス確認が完了したらctrl-cで終了する

別のターミナルでプロセス名を確認
$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
~~~~~
aoishi   31489  9574  2 02:16 pts/2    00:00:00 python test_setproctitle_1.py
aoishi   31513 31489  0 02:16 pts/2    00:00:00 python test_setproctitle_1.py
aoishi   31514 31489  0 02:16 pts/2    00:00:00 python test_setproctitle_1.py

親・子プロセスともにプログラムの実行コマンドがプロセス名として表示されています。name 引数の内容はpsコマンドのプロセス名には反映されないようです。

setproctitleモジュールでプロセス名を変更

setproctitleモジュールをインストールしておきます。

$ pip install setproctitle

先ほどのプログラムに対して、setproctitleモジュールを使ってプロセス名を設定してみたのが下記になります。

import setproctitle
from multiprocessing import Process, current_process
import time

def child_process():
    setproctitle.setproctitle(current_process().name)
    while True:
        time.sleep(1)

def main():
    setproctitle.setproctitle('parent process')

    try:
        p1 = Process(target=child_process, name='child process 1')
        p1.daemon = True
        p1.start()

        p2 = Process(target=child_process)
        p2.daemon = True
        p2.start()

        while True:
            time.sleep(1)

    except:
        p1.terminate()
        p2.terminate()

if __name__ == '__main__':
    main()

変更点は下記になります。

  • setproctitleモジュールをimportした
  • multiprocessingモジュールからcurrent_processメソッドをimportした(child_process内で自身のProcessオブジェクトを参照するため)
  • child_processメソッド内で、setproctitle.setproctitleメソッドを使って子プロセス名をname引数で渡された文字列に設定した
  • mainメソッド内で、setproctitle.setproctitleメソッドを使って親プロセス名を設定した

下記が、先ほどと同じように実行して確認した結果です。

$ python test_setproctitle_2.py
※ 下記の別ターミナルでのプロセス確認が完了したらctrl-cで終了する

別のターミナルでプロセス名を確認
$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
~~~~~
aoishi   31633  9574  2 02:16 pts/2    00:00:00 parent process
aoishi   31657 31633  0 02:16 pts/2    00:00:00 child process 1
aoishi   31658 31633  0 02:16 pts/2    00:00:00 Process-2

今度はプロセス名が親・子プロセスとで変わっています。プロセス名を明示的に指定していないp2プロセスについてはプロセス名が Process-2 となっていますが、下記ドキュメントにも記載されているように、name引数を指定しなかった場合の仕様のようです。

https://docs.python.jp/3/library/multiprocessing.html#multiprocessing.Process.name

子プロセスは親プロセス名を引き継ぐ

おまけ的な内容になりますが、子プロセスは親プロセスのプロセス名を引き継ぐようです。 子プロセス側で変更していなければ、コピーオンライトで親プロセスとページ共有されるので、よくよく考えたら同じであっても全く不思議ではないですね。。。

確認のために、親プロセスだけsetproctitleモジュールでプロセス名を設定するようにしてみました。

import setproctitle
from multiprocessing import Process, current_process
import time

def child_process():
    while True:
        time.sleep(1)

def main():
    setproctitle.setproctitle('parent process')

    try:
        p1 = Process(target=child_process, name='child process 1')
        p1.daemon = True
        p1.start()

        p2 = Process(target=child_process)
        p2.daemon = True
        p2.start()

        while True:
            time.sleep(1)

    except:
        p1.terminate()
        p2.terminate()

if __name__ == '__main__':
    main()

これまでと同じように実行してみます。

$ python test_setproctitle_3.py
※ 下記の別ターミナルでのプロセス確認が完了したらctrl-cで終了する

別のターミナルでプロセス名を確認
$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
~~~~~
aoishi    3328  9574  2 02:49 pts/2    00:00:00 parent process
aoishi    3329  3328  0 02:49 pts/2    00:00:00 parent process
aoishi    3330  3328  0 02:49 pts/2    00:00:00 parent process

2つの子プロセスともに、親プロセスで指定したプロセス名になっています。

まとめ

setproctitleモジュールで簡単にプロセス名を変更できました。

参考

qiita.com

Zabbix Senderを自前実装する場合にはZabbix Protocolのヘッダーが必要だった

はじめに

以前に下記の記事でZabbix Senderを自前実装していましたが、一つのコネクションで一度に大量のデータを送ろうとするとたびたびエラーになることがわかりました。

実装に不備があることもわかったので、正しいと思われる方法についてまとめておきます。

aoishi.hateblo.jp

原因

私の誤った解釈により、Zabbix Senderは決められたJSONフォーマットのデータをTCPでZabbixサーバに送ればOKだと思っていましたが、実はJSONデータの前にZabbixプロトコルのヘッダーをつける必要がありました。

ヘッダーを含めたZabbix Senderの実装としては下記が参考になります。

AWS Lambda(Python)からZabbix Senderでメトリクス値を送るスクリプト | 外道父の匠

ヘッダーについては下記が参考になります。

検知経緯

原因を突き止めるまでに、下記のような事象を確認していました。

Zabbix Senderのレスポンス

まず、Zabbix Senderでのデータ送信に成功した場合には、下記のようなレスポンスが返されました。

{"response":"success","info":"processed: 39; failed: 0; total: 39; seconds spent: 0.093660"}                                                                   
{"response":"success","info":"processed: 33; failed: 0; total: 33; seconds spent: 0.916001"}                                                                   
{"response":"success","info":"processed: 3; failed: 0; total: 3; seconds spent: 0.131982"}                                                                     
{"response":"success","info":"processed: 56; failed: 0; total: 56; seconds spent: 0.690923"}

次に、失敗した場合のレスポンスが下記の通りです。

[1回目]
{"response":"failed","info":"cannot parse as a valid JSON object: invalid object name/value separator at: ''"}                                                 
{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"}                                                              
{"response":"success","info":"processed: 3; failed: 0; total: 3; seconds spent: 0.093064"}                                                                     
{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"}

[2回目]
{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"}                                                              
{"response":"success","info":"processed: 33; failed: 0; total: 33; seconds spent: 1.029911"}                                                                   
{"response":"success","info":"processed: 3; failed: 0; total: 3; seconds spent: 0.086790"}                                                                     
{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"}  


[3回目]
{"response":"success","info":"processed: 39; failed: 0; total: 39; seconds spent: 0.074564"}                                                                   
{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"}                                                              
{"response":"success","info":"processed: 3; failed: 0; total: 3; seconds spent: 0.085845"}                                                                     
{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"} 

実行する度に成功したり失敗したりと不安定です。

Zabbixサーバのログ

今後は、Zabbixサーバのログを確認してみました。DebugLevel=4に変更しています。

 10279:20180108:010832.540 __zbx_zbx_setproctitle() title:'trapper #2 [processing data]'                                                                                                                                                      
 10279:20180108:010832.540 trapper got '{"data": [{"host": "laptop03", "value": 6.983, "key": "zzz_float_value[dstat.total_cpu_usage.usr]", "clock": "1515341312"}, {"host": "laptop03", "value": 1.496, "key": "zzz_float_value[dstat.total_cpu_usage.sys]", "clock": "1515341312"}, {"host": "laptop03", "value": 91.521, "key": "zzz_float_value[dstat.total_cpu_usage.idl]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.total_cpu_usage.wai]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.total_cpu_usage.stl]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.dsk_nvme0n1.read]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.dsk_nvme0n1.writ]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.paging.in]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.paging.out]", "clock": "1515341312"}, {"host": "laptop03", "value": 1.09, "key": "zzz_float_value[dstat.load_avg.1m]", "clock": "1515341312"}, {"host": "laptop03", "value": 2.84, "key": "zzz_float_value[dstat.load_avg.5m]", "clock": "1515341312"}, {"host": "laptop03", "value": 2.89, "key": "zzz_float_value[dstat.load_avg.15m]", "clock": "1515341312"}, {"host": "laptop03", "value": 12326502400.0, "key": "zzz_float_value[dstat.memory_usage.used]", "clock": "1515341312"}, {"host": "laptop03", "value": 1197580288.0, "key": "zzz_float_value[dstat.memory_usage.free]", "clock": "1515341312"}, {"host": "laptop03", "value": 384909312.0, "key": "zzz_float_value[dstat.memory_usage.buff]", "clock": "1515341312"}, {"host": "laptop03", "value": 3268669440.0, "key": "zzz_float_value[dstat.memory_usage.cach]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.net_wlp4s0.recv]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.net_wlp4s0.send]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.procs.run]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.procs.blk]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.procs.new]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.io_nvme0n1.read]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.io_nvme0n1.writ]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.swap.used]", "clock": "1515341312"}, {"host": "laptop03", "value": 8359243776.0, "key": "zzz_float_value[dstat.swap.free]", "clock": "1515341312"}, {"host": "laptop03", "value": 963.0, "key": "zzz_float_value[dstat.system.int]", "clock": "1515341312"}, {"host": "laptop03", "value": 2470.0, "key": "zzz_float_value[dstat.system.csw]", "clock": "1515341312"}, {"host": "laptop03", "value": 21.0, "key": "zzz_float_value[dstat.tcp_sockets.lis]", "clock": "1515341312"}, {"host": "laptop03", "value": 50.0, "key": "zzz_float_value[dstat.tcp_sockets.act]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.tcp_sockets.syn]", "clock": "1515341312"}, {"host": "laptop03", "value": 4.0, "key": "zzz_float_value[dstat.tcp_sockets.tim]", "clock": "1515341312"}, {"host": "laptop03", "value": 4.0, "key": "zzz_float_value[dstat.tcp_sockets.clo]", "clock": "1515341312"}, {"host": "laptop03", "value": 12.0, "key": "zzz_float_value[dstat.udp.lis]", "clock": "1515341312"}, {"host": "laptop03", "value": 0.0, "key": "zzz_float_value[dstat.udp.act]", "clock": "1515341312"}, {"host": "laptop03", "value": 62.0, "key": "zzz_float_value[dstat.unix_sockets.dgm]", "clock": "1515341312"}, {"host": "laptop03", "value": 1724.0, "key": "zzz_float_value[dstat.unix_sockets.str]", "clock": "1515341312"}, {"host": "laptop03", "value": 638.0, "key": "zzz_float_value[dstat.unix_sockets.lis]", "clock": "1515341312"}, {"host": "laptop03", "value": 1086.0, "key": "zzz_float_value[dstat.unix_sockets.act]", "clock": "1515341312"}, {"host": "laptop03", "value": "{\"data\": [{\"{#KEY_NAME}\": \"dstat.total_cpu_usage.usr\"}, {\"{#KEY_NAME}\": \"dstat.total_cpu_usage.sys\"}, {\"{#KEY_NAME}\": \"dstat.total_cpu_usage.idl\"}, {\"{#KEY_'                  
 10279:20180108:010832.540 In zbx_send_response()          
 10279:20180108:010832.540 zbx_send_response() '{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"}'                                                                                            
 10279:20180108:010832.540 End of zbx_send_response():SUCCEED                                                          
 10279:20180108:010832.540 received invalid JSON object from 10.0.0.245: cannot parse as a valid JSON object: unexpected end of string data
 10279:20180108:010832.541 __zbx_zbx_setproctitle() title:'trapper #2 [processed data in 0.000432 sec, waiting for connection]'

2行目(trapper got)に送信したJSONデータが記録されていましたが、途中で途切れていました。

後続のログでもJSONをパースできない旨のエラーメッセージが記録されていますので、Zabbixサーバが送信したデータを期待通り受け取れていないようです。

tcpdump

最後にtcpdumpで通信の様子を確認してみました。

クライアント側で下記コマンドでtcpdumpします。

$ sudo tcpdump -n -i any port 10051

下記が失敗した場合の結果です。10.0.0.1がZabbixサーバ、10.0.0.245がクライアントです。最後にZabbixサーバからクライアントにリセットパケットが送られているようです。

01:43:13.795474 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [S], seq 2393260655, win 29200, options [mss 1460,sackOK,TS val 4115495880 ecr 0,nop,wscale 7], length 0                                                                 
01:43:13.798023 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [S.], seq 1719041873, ack 2393260656, win 28960, options [mss 1460,sackOK,TS val 1378069742 ecr 4115495880,nop,wscale 7], length 0                                       
01:43:13.798092 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [.], ack 1, win 229, options [nop,nop,TS val 4115495883 ecr 1378069742], length 0                                                                                        
01:43:13.798490 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [.], seq 1:1449, ack 1, win 229, options [nop,nop,TS val 4115495883 ecr 1378069742], length 1448                                                                         
01:43:13.798513 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [.], seq 1449:2897, ack 1, win 229, options [nop,nop,TS val 4115495883 ecr 1378069742], length 1448                                                                      
01:43:13.798519 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [.], seq 2897:4345, ack 1, win 229, options [nop,nop,TS val 4115495883 ecr 1378069742], length 1448                                                                      
01:43:13.798523 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [.], seq 4345:5793, ack 1, win 229, options [nop,nop,TS val 4115495883 ecr 1378069742], length 1448                                                                      
01:43:13.798527 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [P.], seq 5793:5974, ack 1, win 229, options [nop,nop,TS val 4115495883 ecr 1378069742], length 181                                                                      
01:43:13.799338 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [.], ack 1449, win 249, options [nop,nop,TS val 1378069745 ecr 4115495883], length 0                                                                                     
01:43:13.799373 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [.], ack 2897, win 272, options [nop,nop,TS val 1378069745 ecr 4115495883], length 0                                                                                     
01:43:13.799386 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [.], ack 4345, win 295, options [nop,nop,TS val 1378069745 ecr 4115495883], length 0                                                                                     
01:43:13.799683 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [P.], seq 1:142, ack 4345, win 295, options [nop,nop,TS val 1378069745 ecr 4115495883], length 141                                                                       
01:43:13.799710 IP 10.0.0.245.47534 > 10.0.0.1.zabbix-trapper: Flags [.], ack 142, win 237, options [nop,nop,TS val 4115495885 ecr 1378069745], length 0                                                                                      
01:43:13.799732 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [.], ack 5974, win 340, options [nop,nop,TS val 1378069745 ecr 4115495883], length 0                                                                                     
01:43:13.799747 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [F.], seq 142, ack 5974, win 340, options [nop,nop,TS val 1378069745 ecr 4115495883], length 0                                                                           
01:43:13.799760 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [R.], seq 143, ack 5974, win 340, options [nop,nop,TS val 0 ecr 4115495883], length 0                                                                                    
01:43:13.800474 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.47534: Flags [R], seq 1719042015, win 0, length 0

ちなみに、成功した場合は下記のような結果になりました。

01:47:03.611559 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [S], seq 587356983, win 29200, options [mss 1460,sackOK,TS val 4115725695 ecr 0,nop,wscale 7], length 0                                                                  
01:47:03.616337 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [S.], seq 1228978652, ack 587356984, win 28960, options [mss 1460,sackOK,TS val 1378299561 ecr 4115725695,nop,wscale 7], length 0                                        
01:47:03.616385 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [.], ack 1, win 229, options [nop,nop,TS val 4115725700 ecr 1378299561], length 0                                                                                        
01:47:03.616660 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [.], seq 1:1449, ack 1, win 229, options [nop,nop,TS val 4115725700 ecr 1378299561], length 1448                                                                         
01:47:03.616674 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [.], seq 1449:2897, ack 1, win 229, options [nop,nop,TS val 4115725700 ecr 1378299561], length 1448                                                                      
01:47:03.616676 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [.], seq 2897:4345, ack 1, win 229, options [nop,nop,TS val 4115725700 ecr 1378299561], length 1448                                                                      
01:47:03.616678 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [.], seq 4345:5793, ack 1, win 229, options [nop,nop,TS val 4115725700 ecr 1378299561], length 1448                                                                      
01:47:03.616685 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [P.], seq 5793:5967, ack 1, win 229, options [nop,nop,TS val 4115725700 ecr 1378299561], length 174                                                                      
01:47:03.619438 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [.], ack 1449, win 249, options [nop,nop,TS val 1378299565 ecr 4115725700], length 0                                                                                     
01:47:03.620498 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [.], ack 2897, win 272, options [nop,nop,TS val 1378299565 ecr 4115725700], length 0                                                                                     
01:47:03.620749 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [.], ack 4345, win 295, options [nop,nop,TS val 1378299566 ecr 4115725700], length 0                                                                                     
01:47:03.620785 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [.], ack 5793, win 317, options [nop,nop,TS val 1378299566 ecr 4115725700], length 0                                                                                     
01:47:03.624183 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [.], ack 5967, win 340, options [nop,nop,TS val 1378299566 ecr 4115725700], length 0                                                                                     
01:47:03.850201 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [P.], seq 1:106, ack 5967, win 340, options [nop,nop,TS val 1378299795 ecr 4115725700], length 105                                                                       
01:47:03.850298 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [.], ack 106, win 229, options [nop,nop,TS val 4115725934 ecr 1378299795], length 0                                                                                      
01:47:03.850357 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [F.], seq 106, ack 5967, win 340, options [nop,nop,TS val 1378299795 ecr 4115725700], length 0                                                                           
01:47:03.850643 IP 10.0.0.245.48794 > 10.0.0.1.zabbix-trapper: Flags [F.], seq 5967, ack 107, win 229, options [nop,nop,TS val 4115725934 ecr 1378299795], length 0                                                                           
01:47:03.851771 IP 10.0.0.1.zabbix-trapper > 10.0.0.245.48794: Flags [.], ack 5968, win 340, options [nop,nop,TS val 1378299797 ecr 4115725934], length 0

ApacheのScoreboardをモニタリングする

はじめに

私事ですが、昨年末に運用しているWEBサービスにて、Apacheの同時接続数が上限に達し一時的にサービス提供できなくなる経験をしました。 その際に、エラーログにScoreboardという文字列を含む下記のエラーログが頻発していました。

AH00286: server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting
AH00287: server is within MinSpareThreads of MaxRequestWorkers, consider raising the MaxRequestWorkers setting
AH00288: scoreboard is full, not at MaxRequestWorkers

対応の一環で、Scoreboadのモニタリング設定などをしたので、その際の内容をまとめておきたいと思います。 また、私の理解不足で不正確な内容を記載している箇所もあるかと思いますが、自分の外部記憶装置代わり的な意味でも記載していますのでご了承下さい。

検証環境

用語

Slot

ApacheがHTTPリクエストを処理する実体のことで、MPMがPreforkであればプロセスのことであり、Worker, Eventであればスレッドのこと。

Scoreboard

Apacheが親プロセスと子プロセスとの間で共有するメモリ領域のこと。各Slot毎にさまざまな情報を保持しているらしい。

下記あたりのコードを見れば、Scoreboardについて詳細にわかりそう。

Scoreboardの状態確認方法

調査すると、mod_statusモジュールでステータスページを通じて確認する方法と、共有メモリ上のScoreboardに直接アクセスして情報を取得する方法があるようです。 今回は、前者のmod_statusモジュールを使った方法を記載します。

後者の共有メモリに直接アクセスする方法は情報があまり多くなく、Scoreboardについて実装レベルで把握していないと難しいようだったので、今回は断念しました。 ただ、mod_statusモジュールを使う場合と比較して、モニタリングのためにApacheにアクセスする必要がないため、接続数が飽和している状態でもScoreboardの状態が確認できるという点でメリットがありそうです。

参考までに、下記に記事を紹介しておきます。

mod_statusの有効化とステータスページからのデータ取得

下記の内容をApacheの設定ファイルに追記します。これにより、Apacheの状態がステータスページで確認できるようになります。

LoadModule status_module modules/mod_status.so
ExtendedStatus On

Listen 81
<VirtualHost *:81>
    <Location /server-status>
        SetHandler server-status
        Require all granted
    </Location>
</VirtualHost>

ここでは、ステータスページのURIがサービスに干渉することを防ぐために、VirtualHostで未使用のポートに対してステータスページを表示させるよう設定しています。 モニタリングや通信制限の運用次第ではありますが、接続元をlocalhostなどに絞るなどのセキュリティ対策は実際の環境に応じて検討したほうが良いと思います。

また、.htaccessなどの設定ファイル内でもステータスページの表示設定ができるようになるため、.htaccessを有効している環境では意図せずステータスページを外部に公開してしまう恐れもあるため注意が必要です。回避策としては、AllowOverrideやAllowOverrideListなどでSetHandlerの使用を許可しないよう設定すれば良さそうです。

https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride

設定ファイルを編集後、Apacheを再起動します。

$ apachectl -t
Syntax OK

$ sudo systemctl restart httpd

ローカルからステータスページにアクセスしてみます。先のVirtualHostで定義したURLに対して?autoパラメタをつけてアクセスすることで、下記のようなkey-value形式でデータを取得できます。

$ curl localhost:81/server-status?auto
localhost
ServerVersion: Apache/2.4.28 (CentOS) OpenSSL/1.0.2k-fips
ServerMPM: worker
Server Built: Oct  9 2017 12:34:18
CurrentTime: Monday, 01-Jan-2018 16:11:40 JST
RestartTime: Monday, 01-Jan-2018 16:08:56 JST
ParentServerConfigGeneration: 1
ParentServerMPMGeneration: 0
ServerUptimeSeconds: 163
ServerUptime: 2 minutes 43 seconds
Load1: 0.04
Load5: 0.12
Load15: 0.22
Total Accesses: 305
Total kBytes: 361
CPUUser: .39
CPUSystem: .18
CPUChildrenUser: 0
CPUChildrenSystem: 0
CPULoad: .349693
Uptime: 163
ReqPerSec: 1.87117
BytesPerSec: 2267.88
BytesPerReq: 1212.01
BusyWorkers: 1
IdleWorkers: 49
Scoreboard: __________________W_______________________________
TLSSessionCacheStatus
CacheType: SHMCB
CacheSharedMemory: 512000
CacheCurrentEntries: 76
CacheSubcaches: 32
CacheIndexesPerSubcaches: 88
CacheTimeLeftOldestAvg: 183
CacheTimeLeftOldestMin: 136
CacheTimeLeftOldestMax: 288
CacheIndexUsage: 2%
CacheUsage: 3%
CacheStoreCount: 76
CacheReplaceCount: 0
CacheExpireCount: 0
CacheDiscardCount: 0
CacheRetrieveHitCount: 0
CacheRetrieveMissCount: 0
CacheRemoveHitCount: 0
CacheRemoveMissCount: 0

ここで、目的のScoreboardは下記のような文字列として表示されており、1文字が1つのSlotの状態を表現しています。

Scoreboard: __________________W_______________________________

各文字とSlotの状態の意味は下記の通りです。

文字 状態名 意味
_ Waiting for Connection プロセス/スレッド起動し、アクセス待ちの状態
S Starting up プロセス/スレッドが起動中の状態
R Reading Request クライアントからのアクセスを受け付けて、リクエストを読み込んでいる状態
W Sending Reply クライアントにレスポンスを返している状態
K Keepalive (read) KeepAliveによりリクエストを待機している状態
D DNS Lookup クライアントのIPの名前解決している状態
C Closing connection 接続を終了している状態
L Logging ログ出力している状態
G Gracefully finishing スレッドのgracefulな停止処理中またはそこからの起動中 ?
I Idle cleanup of worker スレッドが終了中な状態
. Open slot with no current process 空きSlot状態

何度かステータスページにアクセスしていると気づきますが、アクセスが全くない状態でもScoreboardには必ず1つWが表示されます。これはステータスページへのアクセスを処理しているSlotの状態を示しており、そのレスポンス中のScoreboardの状態が取得されステータスページが生成されているためだと思います。

Scorboardの数値化

ステータスページに表示されていたScoreboardは1文字が1つのSlotの状態を表現した文字列として表示されるため、その意味に従って状態毎にカウントして数値化することでモニタリングできるようになります。

ここでは、下記のPythonスクリプトでステータスページのScoreboardの文字列を数値化してみます。

#!/bin/env python

import requests
import argparse

scoreboard_lookup = {
    '_': 'WaitingForConnection',
    'S': 'StartingUp',
    'R': 'ReadingRequest',
    'W': 'SendingReply',
    'K': 'KeepAlive',
    'D': 'DNSLookup',
    'C': 'ClosingConnection',
    'L': 'Logging',
    'G': 'GracefullyFinishing',
    'I': 'IdleCleanupOfWorker',
    '.': 'OpenSlotWithNoCurrentProcess',
}

def main(url):
    r = requests.get(url)

    for key, value in [ tuple(x) for x in [ line.split(': ') for line in r.text.splitlines() ] if len(x) == 2]:
        key = key.replace(' ', '')
        if key == 'Scoreboard':
            print('apache.scoreboard.{},{}'.format('TotalSlot', len(value)))
            for scoreboard_char,scoreboard_label in scoreboard_lookup.items():
                print('apache.scoreboard.{},{}'.format(scoreboard_label, value.count(scoreboard_char)))
        else:
            print('apache.{},{}'.format(key, value))

if __name__ == '__main__':

    argparser = argparse.ArgumentParser()
    argparser.add_argument('-u', '--url', type=str, required=True)

    args = argparser.parse_args()
    main(args.url)

上記スクリプトは、ステータスページの内容を取得して下記の処理をしています。

  • csv形式のkey-value(key,value)形式に整形
  • keyのprefixにapache.を追加
  • keyに含まれていた半角スペースを削除
  • ScoreboardをSlotの状態毎にカウントし、apache.scoreboard.状態名というkeyのvalueに追加
  • Scoreboardの総Slot数(apache.scoreboard.TotalSlot)を追加

適当にファイル名でスクリプトを作成し、実行権限を付与して実行します。

$ vi apache.py
$ chmod 755 apache.py
$ apache.py -u http://pxy01.private:81/server-status?auto
apache.ServerVersion,Apache/2.4.28 (CentOS) OpenSSL/1.0.2k-fips                                                                
apache.ServerMPM,worker                                        
apache.ServerBuilt,Oct  9 2017 12:34:18                        
apache.CurrentTime,Monday, 01-Jan-2018 17:53:38 JST            
apache.RestartTime,Monday, 01-Jan-2018 16:08:56 JST            
apache.ParentServerConfigGeneration,1                          
apache.ParentServerMPMGeneration,0                             
apache.ServerUptimeSeconds,6282                                
apache.ServerUptime,1 hour 44 minutes 42 seconds               
apache.Load1,0.00                                              
apache.Load5,0.01                                              
apache.Load15,0.05                                             
apache.TotalAccesses,11589                                     
apache.TotalkBytes,13357                                       
apache.CPUUser,16.41                                           
apache.CPUSystem,6.45                                          
apache.CPUChildrenUser,0                                       
apache.CPUChildrenSystem,0                                     
apache.CPULoad,.363897                                         
apache.Uptime,6282                                             
apache.ReqPerSec,1.84479                                       
apache.BytesPerSec,2177.26                                     
apache.BytesPerReq,1180.22                                     
apache.BusyWorkers,1                                           
apache.IdleWorkers,49                                          
apache.scoreboard.TotalSlot,50                                 
apache.scoreboard.ClosingConnection,0                          
apache.scoreboard.DNSLookup,0                                  
apache.scoreboard.GracefullyFinishing,0                        
apache.scoreboard.IdleCleanupOfWorker,0                        
apache.scoreboard.KeepAlive,0                                  
apache.scoreboard.Logging,0                                    
apache.scoreboard.StartingUp,0                                 
apache.scoreboard.ReadingRequest,0                             
apache.scoreboard.SendingReply,1                               
apache.scoreboard.WaitingForConnection,49                      
apache.scoreboard.OpenSlotWithNoCurrentProcess,0               
apache.CacheType,SHMCB                                         
apache.CacheSharedMemory,512000                                
apache.CacheCurrentEntries,137                                 
apache.CacheSubcaches,32                                       
apache.CacheIndexesPerSubcaches,88                             
apache.CacheTimeLeftOldestAvg,65                               
apache.CacheTimeLeftOldestMin,0                                
apache.CacheTimeLeftOldestMax,222                              
apache.CacheIndexUsage,4%                                      
apache.CacheUsage,5%                                           
apache.CacheStoreCount,2883                                    
apache.CacheReplaceCount,0                                     
apache.CacheExpireCount,2746                                   
apache.CacheDiscardCount,0                                     
apache.CacheRetrieveHitCount,0                                 
apache.CacheRetrieveMissCount,0                                
apache.CacheRemoveHitCount,0                                   
apache.CacheRemoveMissCount,0

上記apache.scoreboard.あたりがScoreboardの状態が数値化したものになります。 私の場合は、上記のkey-valueのデータをZabbix SenderでZabbixサーバに送ることでグラフ化したりしています。

モニタリングツールでScoreboardをモニタリングする参考リンク

私自身ですべて試したことがあるわけではありませんが、有名どころのモニタリングツールを使ってScoreboardをモニタリングする方法の関連リンクを紹介しておきます。

Zabbix

Datadog

mackerel

Prometheus

Pythonでネストを減らす(if文によるdictのキーチェック編)

やりたいこと

  • PythonJSONないしはYAMLで記述された設定ファイルを読み込みたい
  • 読み込んだ設定ファイルをdictとして扱い、キーが存在したら何らかの処理をしたい
  • キーチェックのためのネストを減らしたい

元の方法

YAMLで記述された設定ファイルを読み込み、item_listキーが存在する場合にのみ、キーに対するリストに対してループ処理をするサンプルです。

for文でループ処理する前にif文でキーチェックをしているため、ネストが深くなってしまっています。

with open(config_file) as f:
    config = yaml.load(f)
    if 'item_list' in config:
        for item in config['item_list']:
            # ここでやりたい処理を記述

三項演算子を使って改善した方法

Python三項演算子を使うことで、if文のキーチェックをfor文の行に持ってくることができます。

with open(config_file) as f:
    config = yaml.load(f)
    for item in config['item_list'] if 'item_list' in config else list():
        # ここでやりたい処理を記述

上記のように記述することで、

  • item_listキーが存在する場合は、そのキーに対するリストに対してループ処理
  • item_listキーが存在しない場合は、空のリストに対してループ処理(つまり、処理をスキップさせる)

ことができます。

dictのgetを使って改善した方法

dictのgetメソッドを使うことで、さらにシンプルに記載できます。

with open(config_file) as f:
    config = yaml.load(f)
    for item in config.get('item_list', list()):
        # ここでやりたい処理を記述

dictのgetメソッドでは、get(key[, default]) のように記述でき、下記の振る舞いをします。

  • dictにkeyが存在していれば、それに対する値を返す
  • dictにkeyが存在していなければ、defaultを返す
    • defaultが与えられない場合は、Noneを返す

参考

Pythonとcronで秒単位で定期処理する

やりたいこと

  • Pythonスクリプトを定期的に実行したい
  • cronで手軽に定期処理したい
  • 秒単位で定期実行したい

解決方法

Pythonスクリプト作成

下記のように、実行間隔(秒)と実行回数をコマンドライン引数で与えて、Pythonスクリプトの中で繰り返し実行できるようにします。

import argparse
from datetime import datetime
import time

# ここに定期実行したい処理を記載
def main():
    pass

if __name__ == '__main__':
    argparser = argparse.ArgumentParser()
    argparser.add_argument('-i', '--interval', type=int, default=1)
    argparser.add_argument('-c', '--count', type=int, default=1)

    args = argparser.parse_args()
    _start_time = 0
    for i in range(args.count):
        while int(datetime.now().strftime('%s')) - _start_time < args.interval:
            time.sleep(0.1)
        _start_time = int(datetime.now().strftime('%s'))

        # ここで定期実行したい処理を呼び出す
        main()

Pythonスクリプトをcronに設定

例えば、5秒毎に処理をしたい場合には、下記のようにcronの設定をします。

$ crontab -e
----
* * * * * /path/to/python_script -i 5 -c 12
----

Zabbix Sender + LLD(ローレベルディスカバリ) + dstat + Grafanaでリソースモニタリング

はじめに

Zabbix Advent Calendar 2017 の3日目の投稿です。

リソースモニタリング方法については、色々な手法が提案されていると思いますが、今回はZabbix SenderとLLD(ローレベルディスカバリ)を使い、dstatで得られるデータを収集・グラフ化してみたいと思います。

こだわりポイントとしては2つあります。

  • dstatはオプション次第でさまざまなデータを取得することが可能です。そこで、オプション変更の都度テンプレートを修正しなくても良いように、LLDを使ってdstatのデータからアイテムをディスカバリさせます。

  • モニタリングするための導入物を減らすためにZabbixで提供されているzabbix_senderコマンドは使わず、Zabbix SenderのプロトコルPythonで独自*1に実装します。

目次

Zabbixサーバ側の準備

まず、Zabbixサーバ側の設定をします。

LLD(ローレベルディスカバリ)用の汎用的なテンプレートを作成します。

ホストグループ作成

今回の環境準備のために、ホストグループ作成します。

特にこの名前のグループでないとダメといったことはありませんので、既存のホストグループを利用しても結構です。

ここでは、下記の名前でグループを作成することとします。

項目 設定値
グループ名 Universal Sender servers

テンプレートの作成

ディスカバリルールを作成するためのテンプレートを作成します。

ホストに対してディスカバリルールを作成することもできそうですが、複数台展開のことを考慮してテンプレート化した方が後々楽できると思います。

下記のテンプレート名、そして先ほど作成したホストグループに所属させます。

項目 設定値
テンプレート名 Template Universal Sender
グループ Universal Sender servers

ディスカバリルールの作成

先に作成したテンプレートに対してディスカバリルールを作成します。

「作成したテンプレートを選択 > ディスカバリルール > ディスカバリルールの作成」で作成画面に遷移できます。

Zabbix Senderを利用するので、タイプはZabbix トラッパーとします。キーはスクリプトでZabbixサーバにデータを送る際に使うため、間違えないよう注意してください。

項目 設定値
名前 universal sender discovery
タイプ Zabbix トラッパー
キー universal.discovery

アイテムプロトタイムの作成

ディスカバリルールと同じく、先に作成したテンプレートに対してアイテムプロトタイプを作成します。

「作成したディスカバリルールを選択 > アイテムのプロトタイプ > アイテムのプロトタイプ作成」で作成画面に遷移できます。

ディスカバリルールと同様に、タイプはZabbix トラッパーとします。後にデータを送るときやグラフ化する際に名前とキーを使うため、ここも間違えないよう注意してください。

データ型は、数値データをまとめて扱うために数値(浮動小数)としています。

項目 設定値
名前 {#KEY_NAME}
タイプ Zabbix トラッパー
キー universal_sender[{#KEY_NAME}]
データ型 数値(浮動小数

ホストの作成とテンプレート割り当て

最後に、ホストを作成して先に作成したテンプレートを割り当てます。

項目 設定値
ホスト名 test_server
グループ Universal Sender servers
テンプレート Template Universal Sender

クライアント側の準備

スクリプトの配置

下記コマンドで、モニタリング対象のホストにPythonスクリプトを配置します。 dstatだけインストールされていれば、Python2,3どちらの環境でも追加パッケージ不要で実行できるはずです。

ZABBIX_SERVERとZABBIX_PORTについては、各環境に合わせて変更してください。

$ cat <<EOF > universal_sender_dstat.py
import json
import socket
import re
import csv
import subprocess
import tempfile
from datetime import datetime

# change for your environment.
ZABBIX_SERVER = "10.0.0.1"
ZABBIX_PORT = 10051


ZABBIX_HOST = 'test_server'
DSTAT = 'dstat -cdglmnprsy --tcp --udp --unix --nocolor --noheaders --output {} 1 2'
KEY_NAME = '{#KEY_NAME}'
DISCOVERY_KEY = 'universal.discovery'
ITEM_KEY = 'universal_sender[{}]'

def get_data():

  with tempfile.NamedTemporaryFile('rw') as f:

      command = DSTAT.format(f.name)
      subprocess.check_output(command, shell=True)

      f.seek(2)
      data = csv.reader(f)

      # skip header rows
      data.next()
      data.next()

      first_header_list = list()
      prev_header = ""
      for row_item in data.next():
          if row_item:
              prev_header = re.sub('\s|/', '_', row_item)
          first_header_list.append(prev_header)

      second_header_list = data.next()

      key_list = [
        'dstat.{}.{}'.format(
          first_header_list[i],
          second_header_list[i]
        ) for i in range(len(first_header_list)) ]

      # discovery data
      discovery_data = [
        {
          'host': ZABBIX_HOST,
          'key': DISCOVERY_KEY,
          'value': json.dumps({'data': [ { KEY_NAME: key } for key in key_list ]})
        }
      ]

      # sender data
      # skip first sample data
      data.next()
      item_data = [
        {
          'host': ZABBIX_HOST,
          'key': ITEM_KEY.format(key_list[i]),
          'clock': datetime.now().strftime('%s'),
          'value': value,
        } for i, value in enumerate(data.next())
      ]

      return (discovery_data, item_data)

def send(data):

    sender_data = {
        'request': 'sender data',
        'data': data,
    }
    # print(json.dumps(sender_data, indent=2, ensure_ascii=False))

    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((ZABBIX_SERVER, ZABBIX_PORT))
    client.sendall(json.dumps(sender_data))
    response = client.recv(4096)

    print('response: {}'.format(response))


if __name__ == '__main__':
  discovery_data, sender_data = get_data()

  send(discovery_data)
  send(sender_data)
EOF

なお、Zabbix Senderのプロトコルは下記フォーマットのJSONデータをSocketで送るだけの非常にシンプルな作りになっています。

[LLDのデータを送信する場合]
{
  "request": "sender data",
  "data": [
    {
      "host": "test_server", 
      "value": "{\"data\": [{\"{#KEY_NAME}\": \"dstat.total_cpu_usage.usr\"}, ... ]}", 
      "key": "universal.discovery"
    }
  ]
}

[アイテムのデータを送信する場合]
{
  "request": "sender data",
  "data": [
    {
      "host": "test_server", 
      "value": "39.547", 
      "key": "universal_sender[dstat.total_cpu_usage.usr]", 
      "clock": "1512231561"
    },
    .
    .
    .

  ]
} 

どちらのデータも基本的には同じフォーマットですが、LLDのvalueはディスカバリによって作成されるアイテム名のJSONオブジェクトが、dumpされた文字列となっているので注意してください。

モニタリング開始

データ取得の開始

下記のコマンドで、データ収集を開始します。

$ watch -n 10 python universal_sender_dstat.py

正常に動作した場合、Zabbix Senderプロトコルで通信した際のレスポンスとして、下記のメッセージが出力されます。

ZBXD\{"response":"success","info":"processed: 1; failed: 0; total: 1; seconds spent: 0.006614"}
ZBXD\{"response":"success","info":"processed: 38; failed: 0; total: 38; seconds spent: 0.000635"}

データ送信に失敗した場合は、下記のメッセージが出力されます。

ZBXD\{"response":"success","info":"processed: 0; failed: 1; total: 1; seconds spent: 0.006340"}
ZBXD\{"response":"success","info":"processed: 0; failed: 38; total: 38; seconds spent: 0.000724"}

プロトコルとしては正しく通信できているが、キーが異なるかZabbixサーバ側でまだアイテムが作成されていないために、データを受け取れていないと思われます。

また、プロトコルが不正(dstatコマンドの結果のパースに失敗してJSONデータがおかしくなった場合など)の場合には、下記のようなメッセージが表示されます。

ZBXDa{"response":"failed","info":"cannot parse as a valid JSON object: unexpected end of string data"}

データ取得結果の確認

まずは、ホストのアイテム一覧でディスカバリで作成されたアイテムが存在することを確認します。

ステータスが正常となっていれば問題ありません。

f:id:akihisa_oishi:20171202232537p:plain

次に、ホストの最新データを確認し、ディスカバリで作成されたアイテムについてデータが取得できていることを確認します。

f:id:akihisa_oishi:20171202233318p:plain

データが取得できていれば、取得時刻と値が表示されます。

取得したデータのグラフ化

Grafanaでダッシュボードを作成して取得したデータをグラフ化します。

Grafanaの導入および、Zabbixのデータソース設定方法は既に記事が多数ありますのでここでは割愛します。

最終的な見た目は下記のようになります。 f:id:akihisa_oishi:20171203003002p:plain

ダッシュボードはテンプレート機能を利用し、Zabbixのクエリでホストを選択できるようにします。

項目 設定値 説明
Name Group Host
Type Query Query
Data source Zabbixのデータソースを指定 Zabbixのデータソースを指定
Query {Universal Sender servers} {Universal Sender servers}{*}
Query の意味 Universal Sender servers のみ選択できる Universal Sender servers グループのホストが選択できる

f:id:akihisa_oishi:20171203003644p:plain

グラフ作成画面にて、テンプレート機能で作成した変数およびディスカバリで作成されたアイテム名を入力し、グラフを作成します。

項目 設定値 説明
Group $Group テンプレート機能で選択されたホストグループの変数
Host $Host テンプレート機能で選択されたホストの変数
Item /dstat.*cpu.*/ グラフ化したいアイテム名(ここではCPU系のデータを正規表現でまとめて指定)

f:id:akihisa_oishi:20171203003016p:plain

まとめ

Zabbix Senderでdstatのデータをモニタリングしてみました。

今回の方法には下記の課題がある思いますが、数値データのモニタリングに限ってしまえば妥協できるものかと思います。

  • アイテムプロトタイプを数値(浮動小数)としているため、文字列データが扱えない。
  • トリガーの設定をしようとすると大変。

まだカレンダーに空きがあるようなので、次回は今回の内容を下記観点でブラッシュアップした記事がかければと思っています!

  • watchコマンドではなくcronなどで定期実行されるようにする。
  • dstatでのデータ取得とZabbix Senderでデータ送信する処理を粗結合にし、モニタリング項目追加に拡張性を持たせる。

参考

Zabbix Sender 無しで Zabbix Server にデータを送る - mattintosh note

*1:偉そうなことを言っていますが、既に先人がおりますので、それらを参考にさせていただいています