eBPF+USDTでphpをトレースしてみる、bpftaceとRbBCCでやる

最近プロダクション環境での諸々の調査に使いたいというモチベーションで BPF Performance Tools (Book) 読み進めつつ、提供されているbcc-toolsを試したりbpftraceでlibほげほげの関数パラメータを抜いたりしていた、のだがUSDTを使用したトレースはうまく動かせず悶々としていた。

正直USDTはパッケージ入れておけばいきなりproductionでさくっと使える、という感じでもなく当初の目的からはやや外れているのだが、機構があるのに動かせないというのはなんとももどかしいので、諸々試行錯誤してとりあえず動いた、というころまでの記録を残しておく。対象はphpで。

なぜphpなのかというと、プライベート的にもお仕事的にも馴染みがありワンチャン使える可能性もあるのでは・・という期待と、ツールを試す過程で標準パッケージのphp

$ tplist-bpfcc -l /usr/bin/php
b'/usr/bin/php' b'php':b'request__startup'
b'/usr/bin/php' b'php':b'request__shutdown'
b'/usr/bin/php' b'php':b'compile__file__entry'
b'/usr/bin/php' b'php':b'compile__file__return'
b'/usr/bin/php' b'php':b'function__return'
b'/usr/bin/php' b'php':b'function__entry'
b'/usr/bin/php' b'php':b'execute__entry'
b'/usr/bin/php' b'php':b'execute__return'
b'/usr/bin/php' b'php':b'error'
b'/usr/bin/php' b'php':b'exception__thrown'
b'/usr/bin/php' b'php':b'exception__caught'

を観測したためこれはもしや--enable-dtraceされてるのでは・・となり、試してみたくなったため。

結果的にUbuntuなら先日リリースされた20.04 LTSのイメージで特に独自ビルドなどは頑張らなくてもいくつかのパッケージインストールのみで(一応)動かすこと自体はできることがわかった。

環境

AWS上でUbuntu 20.04 LTS (Focal Fossa) のインスタンスを立てて動かした。

ami

$ echo `curl -s http://instance-data/latest/meta-data/ami-id`
ami-0c1ac8728ef7f87a4 # ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20200423

php

ii  php7.4-cli                       7.4.3-4ubuntu1                    amd64        command-line interpreter for the PHP scripting language

ruby

ii  ruby2.7                          2.7.0-5ubuntu1                    amd64        Interpreter of object-oriented scripting language Ruby

bpf関連

ii  bpfcc-tools                      0.12.0-2                          all          tools for BPF Compiler Collection (BCC)
ii  bpftrace                         0.9.4-1                           amd64        high-level tracing language for Linux eBPF
ii  libbpfcc                         0.12.0-2                          amd64        shared library for BPF Compiler Collection (BCC)
ii  python3-bpfcc                    0.12.0-2                          all          Python 3 wrappers for BPF Compiler Collection (BCC)

サンプルスクリプト

手頃なものがなかったのではるか昔書いた雑スクリプトの先頭に(pidをアタッチする時間を稼ぐために)sleepを追加して試した、今みるとコード的にもなかなかあれだが、まあサイズ的にもコンパクトでいいかな、ということで・・。

https://github.com/egmc/dscheck

実行条件

  • 環境変数 USE_ZEND_DTRACE=1 を設定してDTrace Supportを有効にしたうえでphpスクリプトを実行する
  • ↑で実行したプロセスのpidを対象にトレースを実行する

phpの場合、--enable-dtraceでビルドしただけでは不十分で USE_ZEND_DTRACE=1 で実行しないとDTrace Supportが有効にならない模様。

git.php.net

. Disabled PHP call tracing by default (it makes significant overhead). This may be enabled again using envirionment variable USE_ZEND_DTRACE=1.

上述の通りfocalの標準パッケージは--enable-dtrace自体はされてるようなのでこれでいける

$ /usr/bin/php -i |grep -i dtrace
DTrace Support => available, disabled
$ USE_ZEND_DTRACE=1 /usr/bin/php -i |grep -i dtrace
DTrace Support => enabled

試しに --enable-dtrace なしでビルドしたphpは以下のようになっていた

$ /usr/local/php7.4-no-trace/bin/php -i |grep -i dtrace
DTrace Support => disabled

bpftaceでトレースする

上述のphpスクリプト$ USE_ZEND_DTRACE=1 php dscheck.php)を実行させつつ、別窓でトレースを実行してテスト

probeのパラメータはここで何が取れるのか確認

autoload処理、関数呼び出しが観測できる、arg1はlineno

# BPFTRACE_STRLEN=200 bpftrace -p `pgrep php` -e 'usdt:/usr/bin/php:execute__entry { printf("%s, %d\n", str(arg0), arg1); }'|head -n10
Attaching 1 probe...
/home/ubuntu/dscheck/vendor/autoload.php, 5
/home/ubuntu/dscheck/vendor/composer/autoload_real.php, 74
/home/ubuntu/dscheck/vendor/composer/autoload_real.php, 21
/home/ubuntu/dscheck/vendor/composer/autoload_real.php, 11
/home/ubuntu/dscheck/vendor/composer/ClassLoader.php, 446
/home/ubuntu/dscheck/vendor/composer/autoload_static.php, 38
/home/ubuntu/dscheck/vendor/composer/autoload_static.php, 30
/home/ubuntu/dscheck/vendor/composer/autoload_static.php, 32
/home/ubuntu/dscheck/vendor/composer/ClassLoader.php, 302

compile__file__entryスクリプトコンパイルを覗いてみる(arg0が char *compile_file のはずだがこちらは空になっている・・

# BPFTRACE_STRLEN=200 bpftrace -p `pgrep php` -e 'usdt:/usr/bin/php:compile__file__entry { printf("%s\n", str(arg1)); }'
Attaching 1 probe...
/home/ubuntu/dscheck/vendor/autoload.php
/home/ubuntu/dscheck/vendor/composer/autoload_real.php
/home/ubuntu/dscheck/vendor/composer/ClassLoader.php
/home/ubuntu/dscheck/vendor/composer/autoload_static.php
/home/ubuntu/dscheck/vendor/composer/../swiftmailer/swiftmailer/lib/swift_required.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
/home/ubuntu/dscheck/vendor/composer/../symfony/yaml/Symfony/Component/Yaml/Parser.php
/home/ubuntu/dscheck/vendor/composer/../../src/DsCheck/DsCheck.php
/home/ubuntu/dscheck/vendor/composer/../symfony/yaml/Symfony/Component/Yaml/Inline.php
/home/ubuntu/dscheck/vendor/composer/../symfony/yaml/Symfony/Component/Yaml/Unescaper.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/swift_init.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/../mime_types.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/preferences.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php

なおpidを指定せずにトレースしようとすると以下のように怒られる

# BPFTRACE_STRLEN=200 bpftrace -e 'usdt:/usr/bin/php:compile__file__entry { printf("%s\n", str(arg1)); }'
Attaching 1 probe...
Error finding or enabling probe: usdt:/usr/bin/php:php:compile__file__entry

RbBCCでトレースする

udzuraさんのこちらのエントリでrubybccできるぞ、というのを試してみたく

udzura.hatenablog.jp

こちらのサンプルを写経させて頂き、probe対象を変更してfunction名を取得するだけのサンプルを試してみた。 (というか実際のところはところこちらを先に動かして、あれ、とれるやんけ・・となりbpftrace側でも同様にトレースできることを確認出来たという流れ)

require 'rbbcc'
include RbBCC
pid = ARGV[0]&.to_i || raise("Usage: #{$0} PID")

bpf_text = <<BPF
#include <uapi/linux/ptrace.h>
struct data_t {
    u64 ts;
    char function[128];
};
BPF_PERF_OUTPUT(events);

int do_trace_function_entry(struct pt_regs *ctx) {
    struct data_t data = {};
    data.ts = bpf_ktime_get_ns();
    bpf_usdt_readarg_p(1, ctx, &data.function, sizeof(data.function));
    events.perf_submit(ctx, &data, sizeof(data));
    return 0;
};
BPF

u = USDT.new(pid: pid.to_i)
u.enable_probe(probe: "function__entry", fn_name: "do_trace_function_entry")

b = BCC.new(text: bpf_text, usdt_contexts: [u])
puts("%-18s %s" % ["TIME(s)", "function"])
start = nil
b["events"].open_perf_buffer do |cpu, data, size|
  event = b["events"].event(data)
  start ||= event.ts
  time_s = ((event.ts - start).to_f) / 1_000_000_000
  puts("%-18.9f %s" % [time_s, event.function])
end
Signal.trap(:INT) { puts "\nDone."; exit }
loop { b.perf_buffer_poll }

こんな感じでトレースできる

# ruby phpusdt.rb `pgrep php` | head -n20
TIME(s)            function
0.000000000        main
0.000193415        main
0.000223335        getLoader
0.000248361        loadClassLoader
0.000652416        main
0.000740099        main
0.000760310        getInitializer
0.000781820        Composer\Autoload\{closure}
0.000799492        register
0.000818768        composerRequirea069f98d7345659050bcc164f0155069
0.000888641        main
0.000979718        main
0.000999792        registerAutoload
0.001022639        loadClass
0.001038429        findFile
0.001054807        findFileWithExtension
0.001089370        Composer\Autoload\includeFile
0.002951398        main
0.002981065        __construct
Possibly lost 227 samples

pythonバインディングもそれほどコード書くわけではないのだけど、gemでさくっと自前のbccツールを、みたいなのが出来るのはいいかもしれない・・。

ということで

なんとかphpでUSDTを使用したbcc/bpftraceでのトレースを実行することができた。

少なくともfocalではphpの再ビルドせずに使うことはできるものの、依然条件は厳しく、PHPであればnewrelic等のAPM使うなど、そこまでこれでなにかを観測したいという強いモチベーションもなく、どうかなーというところですが。

compile__file__xxx でopcodeのキャッシュ状況みるとかワンチャンどうかなーと思いつつ、実際phpスクリプトが動いてる環境はmod-phpであったりphp-fpmであったりするので、そのあたりは結局動かせておらず

ついでにメモ

github.com

bccのレポジトリにも各言語のトレースサンプルが存在している、がこちらは

bpf_probe_read_user missing (added in Linux 5.5).

ということでカーネル5.4.0なfocalでは動作しなかった。

ホームページのメンテナンスをして大体15年目になった話

ウェブサイトというかホームページという方がしっくり来るよねというタイトルで。 技術的なものはなし。

高校からの友人でジャズギタリストである佐津間純のサイトを2005年に作成し、引っ越し、リニューアルなどを経て大体15年目になった。 現状は言ってしまえばまあただのwordpressサイトで、何がしかのサービスの面倒みてました!みたいな話ではないのだけど、一般的なサイトであっても長くメンテしていればそれなりに出来事もあるということで。

junsatsuma.com

レンサバでシージーアイなところからスタートし、2020に至ってついにhttp2を喋るようになり、自分がメンテしてたどのサイト、個人サービスより長くなったのでまあまあ感慨深いなーということで、インターネッツアーカイブを発掘しながら半分記録的な感じでだらだらと書く。

サーバ遍歴としては

さくらのレンサバ->ロリポ->さくらのVPS(CentOS)->Amazon Lightsail(Ubuntu Bionic)

で計3回引っ越しをしている。

その間に自分は

HTML/CSSがちょい書けるだけの専門学生 -> SIer -> web系のサーバサイドエンジニア ->フロント以外全部みるやつ -> インフラエンジニア

という徐々に低レイヤー技術に近寄っていっており、なんとなくそのときどきのトレンドであったり、思考であったりが反映された感じになってる。

以下記録兼思い出的なもの

作成時点で自分はいちおう音楽系の専門学生でウェッブデザイン的なものをぼんやりと考えており、コードは全然書けない状態。

f:id:egmc:20200301214653p:plain

2005年当時のドメインhttp://junsatsuma.sakura.ne.jp/  でさくらのレンサバを使ってる。このアドレス今でもアクセスするとリダイレクトしてくれるんだけど管理はどうなっているんだっけな・・。

$ curl -I http://junsatsuma.sakura.ne.jp/
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Sun, 01 Mar 2020 13:06:32 GMT
Content-Type: text/html; charset=iso-8859-1
Connection: keep-alive
Location: http://junsatsuma.com/

アイコンとかは専門の同級生にお願いして作ってもらったやつ。 シージーアイは魔法の世界なので適当に755とか設定してjcode.plをアレしてFFFTPでテキストモードで転送して、とかやると動くと思っているのでそういうのが動いてる。 マウスオーバーするとアイコンが動いたりする。なんかそういう時代だったやつ。

独自ドメインは2008年に取得している。 自分の状態としては就職してSIerになっており主にJavaなどを書きはじめているもののまだウェッブの世界とは言えない時期。

   Domain Name: JUNSATSUMA.COM
   Registry Domain ID: 1529192299_DOMAIN_COM-VRSN
   Registrar WHOIS Server: whois.enom.com
   Registrar URL: http://www.enom.com
   Updated Date: 2019-11-20T00:29:13Z
   Creation Date: 2008-11-19T01:22:28Z

f:id:egmc:20200301214643p:plain

アクセスカウンターがついてるのも時代を感じる。 まださくらのレンサバを引き続き使っている。

2013年にさくらからロリポップに引っ越しをしている。 リニューアルということでwordpressで作り直していて、テンプレは大変時代を感じるbootstrapベースのものをカスタマイズして使っている。 ロゴは例によって友人に頼んで作成してもらった。

bootstrapベースのジャズミュージシャンのサイトというのは割とレアなんでないかと思う。

f:id:egmc:20200301222432p:plain

デザイン面ではこの時点から変更していないのだけど2020年でもそれなりに動いていて長持ちする記述は悪くないなーと思える。 ロリポを採用してるのはなんでだろう。

この時点の自分のスキルとしてはサーバサイドのphpコードはそれなりに書けてインフラもいちおう構築はできる、みたいな状態なのだけど、レンサバのほうが管理が任せられていいだろう的なところでこうしていたのかもしれない。

その後2014年頃にwordpressで踏みがちな外部からサイトを一部改ざんされる事件があり、対応自体はできたものの緊急退避的に個人サービスを動かしていたさくらVPSサーバに引っ越しをして、結局そのまま使い続けることになった。

なお改ざん事件の対応自体はロリポのサポートも親切かつ技術面でもきちんと見てくれおり、割と安プランなのにありがたいなーと思ったものです。

2019年春にフルhttps対応を行って(よくあるあれとしてfacebookのいいねがリセットされたりしつつ)そのタイミングでhttp2を喋るようにするつもりだったのだけど、さすがに環境が古くてビルドもまわりも色々とめんどくさく、かつ同居してるサービスの更新もしんどかったので引越し先をAWSのLightsailにすることにして、結局先日ようやく引っ越しが完了した。

とりあえず現行のサーバはubuntu bionicでprefork mpmでmod_phpapacheからevent-mpmでphp-fpmを使うapacheとなり、無駄にサーバ監視も充実し、ミドルウェアバージョンアップはあるもののwordpressはきっと、10年後もメンテされてるでしょうということでゆるゆるとメンテし続けていけるのではないかと思っている。 ただいかんせん興味のレイヤーが下がってきてしまったので今からフロントをリニューアル、みたいなことをやろうとするとちょっとしんどい気がする。

個人サービスとかアプリとかちょろちょろメンテしてたけど、ただのウェブサイトでも一つ長期間メンテしているものがある、というのはそれはそれで自身の振り返りもなってよいものですね。

おまけ

ジャズミュージシャンのサイトを方面する層はそもそも年齢層も高く、はてぶユーザーなんて全然いないはずなのになぜか1ブクマ&コメントもついててなんかアイコンに見覚えが。

f:id:egmc:20200302001230p:plain

色々ありますね。

インスタンス内からAmazon Lightsailの裏側を想像してみる(小ネタ)

タイトルどおり特にこれといって役に立つお話ではないのですがなんとなく気になって色々見てみた小ネタ。

きっかけは、Lightsailで起動したubuntuインスタンス内でsnapで入ってるssm-agentが動いており、なんとなく見始めたところから。

TL;DR

  • Lightsailの実態は単にAWS管理下のアカウント内にあるEC2インスタンスっぽい
  • 起動しているインスタンスも例えばUbuntu 18.04のamiならpublicイメージを元に起動してるだけでamiとして特に差分があるわけではない
  • AWSが自社の提供するサービスを組み合わせていい感じにパッケージして定型化したものを別サービスっぽく仕立てて安く提供しているものだと思えばよさそう
  • 単に既存のサービスを組み合わせてターゲットを変えたユーザー層に安価に提供するという戦略なのだろうけどAWS的にも省エネでうまいことできている気がする(使う側からすればまあやすいし)

そういえば

環境

  • LightsailのOS OnlyでUbuntu 18.04
  • ap-northeast-1a
  • メモリ1GB、1 vCPU の5$プラン

f:id:egmc:20200223213625p:plain

見てみたとこ

起動してるssm-agentののログ

AccessDeniedException: User: arn:aws:sts::596322350845:assumed-role/AmazonLightsailInstanceRole/i-095f243859d90fbfd is not authorized to perform: ssm:UpdateInstanceInformation on resource: arn:aws:ec2:ap-northeast-1:596322350845:instance/i-095f243859d90fbfd

おもむろにget-caller-identityしてみる

$ aws sts get-caller-identity
{
    "Arn": "arn:aws:sts::596322350845:assumed-role/AmazonLightsailInstanceRole/i-095f243859d90fbfd",
    "Account": "596322350845",
    "UserId": "AROAYVV4U6367357MRC4Q:i-095f243859d90fbfd"
}

596322350845 はLightsailを使用してる自アカウントではない。 i-095f243859d90fbfd は起動しているインスタンスIDだが 596322350845 アカウント内にあるのでまあ当然自アカウント上では見えない AmazonLightsailInstanceRole というロールがアタッチされてるが特に権限はついてなさそう。 メタデータから普通にクレデンシャルを取ることができるが特に何ができるというわけでもない。

インスタンスタイプ

$ curl http://169.254.169.254/latest/meta-data/instance-type
t2.micro

ami-id

$ curl  http://169.254.169.254/latest/meta-data/ami-id
ami-07ad4b1c3af1ea214

インスタンスは東京リージョンの通常のubuntuイメージ ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 で起動してることがわかる(のでデフォルトでssm-agentも入ってくる)

セキュリティグループ

Your Parkside Resources
Ingress from Your Parkside LoadBalancers
ps-adhoc-10022_22_443_80_9100_9104_9117_9253

が付与されている、インスタンス起動時にこのへんは裏側で動的に作成されるのでしょう、たぶん。

userdata

Lightsail用にCA公開鍵を生成してTrustedUserCAKeysに追加するスクリプトが動いている LightsailのコンソールからターミナルにログインするとAWSのIPからCA認証でログインしたと思われるログが出力されるのでおそらくそれ用のものと思われる

snapshotをとってexportしてみる

Lightsailでsnapshotを取得し、EC2にexportした際に生成されたsnapshotをみると以下のようなdescriptionになっている

Copied for DestinationAmi ami-0d4e5e150b0e9fe87 from SourceAmi ami-06751be45589128e9 for SourceSnapshot snap-0f5472d00cc91a548. Task created on 1,582,454,630,187.

おそらくAWS管理下のアカウントで普通にamiが取得されて管理されていて、Lightsail上で起動する場合は単にそれが使用され、exportするとamiが自アカウントにコピーされてきてSourceAmiのIDはその名残的なところなのかなーと。

ということで

まあ色々眺めて多分Lightsailはこんなかんじなんだろーなーという想像ネタでした。 AWSのサービスは既存の基盤の組み合わせで出来てるものが多くあり、個人的には「別サービスのリソースを実質管理下に置くが手で変更できてしまう」系のサービスがあまり好きではなく、その点Lightsailは(想像ですがおそらく)アカウントごと別ということで必要な機能だけがラッピングされて提供しており割と扱いやすくなっている気がします。

QNAP NAS上でnode_exporterをデーモン化しつつ自動起動

前回prometheus+grafanaを導入してNASのモニタリングも開始してみた、の続きで

dasalog.hatenablog.jp

雑に起動していたQNAP NAS上のnode_exporterをNAS再起動時に自動起動するようにする小ネタ。

やること

これだけでOK。

環境はQNAP TS-220(QTS 4.3.3.0299)

以下順に

雑いinitスクリプト書く

/etc/init.d以下のスクリプトを眺めつつ、こちら

QNAP TS-469Lをmackerelを使ってモニタリングする | misty-magic.h

でmackerel-agentをQNAP上で起動されてる事例を参考に、というか割と流用させて頂きつつとりあえずstop、startだけ出来るスクリプトを用意。 他のスクリプトでも使われている/sbin/daemon_mgrでpid管理などをよしなにやってくれる模様。ちょっと楽。

github.com

/etc/config/qpkg.confに設定を追加して自動起動する

作成した起動スクリプトに実行権限をつけ、起動、停止確認ができたらqpkg.confに設定を追記する。 こんな感じで

[node_exporter]
Name = node_exporter
Version = 0.16.0
Author = prometheus
Date = 2018-05-15
Shell =  /usr/local/node_exporter/etc/init.d/node_exporter.sh
Install_Path = /usr/local/node_exporter
QPKG_File = /usr/local/node_exporter/DUMMY
Enable = TRUE

QPKG_FileはダミーでOK。 Authorとかいろいろ適当だが、meもなにか違うしまあオレオレということで・・。 なお、パス配置は起動スクリプトを素直に/etc/init.d/node_exporter.shに置くとNASの再起動で消えて悲しいことになるので、ひとまず/usr/local/node_exporter以下に諸々配置する構成にしている。

qpkg.confに書いた時点でAppCenter上で見えるようになり、サービスの起動、停止が出来るようになる。

f:id:egmc:20180802001257p:plain

この状態でNASを再起動すると、アプリケーション起動処理の中でnode_exporterも自動で起動されるようになる。

f:id:egmc:20180802001355p:plain

ブラウザで9100を確認、ちゃんと起動している。

f:id:egmc:20180802001803p:plain f:id:egmc:20180802101855p:plain

これで停電があっていつのまにか再起動してたわー的なことがあっても勝手に起動してくれるはず。

2018-08-19追記

再起動では消えないもののQTSのアップデートで/usr/local以下に配置したnode_exporter一式は消えてしまったので、普通にマウントしたディスク領域の共有フォルダ以下に置いた方がよさそう。

/share/MD0_DATA/

以下に置いてひとまず様子見。

prometheus+grafanaで各種サーバとついでにQNAP NASの状態を可視化する

お仕事柄サーバの監視システムを管理、運用しているものの自鯖の監視は適当に導入したmuninやmackerelにお任せという状態だったので、きちんと1から構築してみよう、ということでprometheus+grafanaを導入しつつ、ついでに自宅に置いてるNASのメトリクス取得もやってみたら意外と簡単だったやないかい、というメモ。

三行で

  • prometheus、grafanaを管理サーバ、node_exporterを監視対象サーバでそれぞれ立ち上げ
  • prometheusの設定をちょろっと書いて
  • gradanaにログインしてdatasourceの設定を追加、適当なダッシュボードをimport

でこんな感じのダッシュボードで15秒精度のメトリクスが見れるようになり、なかなか楽しい。

f:id:egmc:20180721235623p:plain
grafana dashboard

構成

  • 監視元&監視対象 GCP上のf1-microなサーバ(ubuntu xenial)
  • 監視対象:さくらVPSサーバ(CentOS)
  • 監視対象:QNAP NAS(TS-220、QTS)

GCP上にprometheusを立ててグローバル越しにnode_exporterを導入したサーバを監視対象にしていく感じで。 デーモン化はsupervisorに任せる方針。

各種設定

node_exporter

バイナリ落としてきて適当に配置して起動するだけ。 config的なものとは特になく、collectorの追加オプションがあるだけなので好みで入れる。 何も指定しなくてもデフォルトで結構取ってくれる。

[program:node_exporter]
command=/usr/local/node_exporter/node_exporter
user=monitor
autorestart=true

雑にsupervisorでデーモン化する 起動後に9100ポートにアクセスして諸々取れてることを確認。

prometheus

バイナリ落としてきて適当に配置してprometheus.yml書くだけ ほかは特にいじらず(おもむろにscrape_configs->static_configsで対象のホスト名:9100を並べていけばOK。

job_nameはgrafanaダッシュボード設定時にラベル合わせればなんでもいいんだけどnode_exporter使うダッシュボードはjob=nodeというラベルを期待してる空気を感じたのでそんな感じにしておくと楽やも。

[program:prometheus]
command=/usr/local/prometheus/prometheus --config.file=/usr/local/prometheus/prometheus.yml
directory=/usr/local/prometheus
user=monitor
autorestart=true

雑にsupervisorでデーモン化する 起動後に9090ポートにアクセスし、適当なクエリを投げ、各ホストからメトリクスが取得できラベルが付与されてることを確認。

grafana

Installing on Debian / Ubuntu | Grafana Documentation

を眺めつつaptで。 3000ポートで起動するのでログインしてdata sourceにprometheusを追加。

であとは適当なダッシュボードを

Grafana Dashboards - discover and share dashboards for Grafana. | Grafana Labs

から探してきてインポートする。 今回はNode Exporter Full 0.16を拝借してみた。

インポートしたダッシュボードで表示されないメトリクスはnode_exporter側でrenameされてたりするので合わせたりする必要があるが大体いい感じに表示される。

https://github.com/prometheus/node_exporter/releases

Many metrics have been renamed/modified to include base units, for example node_cpu is now node_cpu_seconds_total.

とかこういうの。

なおnode_exporterもgrafanaもprometheusもdockerイメージが提供されてるのでそっちを使うのもよさげ。

NASも監視してみる

手持ちのQNAP NASはTS-220というモデルでQTSというLinuxベースのNAS用OSが稼働しており、ここからも各種メトリクスを取得してみる。

f:id:egmc:20180721232649p:plain

(標準のビューでもCPU、メモリ使用率、トラフィック、温度等はなんとなく見れてアプリからも確認できるのでこれはこれでいいのだけど)

TS-220のCPUはMarvell6282(ARMv5)1.6Ghz、でnode_exporterのリリースページをおもむろに覗きにいくとlinux-armv5なバイナリが既に用意されているのでこれを落としてきて起動するだけでイケる。お手軽ですね。 デーモン化はひとまずおいといて、雑にscreen貼って起動しっぱなしにしているが、QTSも/etc/init.d/以下に起動スクリプト置けばイケるはずなのでそのうち対応。

NASは自宅のネットワーク内にいるので9100宛のTCP通信を

  • ルータ側でポートマッピングを行ってNASの9100へ
  • かつインターネット側からのアクセスを監視元のIPに制限(いちおう)

な設定でアクセスできるように。

自宅のインターネット回線は特に固定IPというわけではないので、今回はQNAPが提供するDDNS的なサービス(myqnapcloud)を利用しつつ、自分の管理ドメインから適当にCNAMEを設定して名前解決を行うようにしてみた。

grafanaのビューでみるとこんな感じ。

f:id:egmc:20180721232837p:plain f:id:egmc:20180721232854p:plain

MacのTime Machineバックアップが走ってるときなどはそれなりに書き込みが走ったりするので、ガリガリやってる内訳が観測できて結構楽しい。

gdbで死んでるphpプロセスから諸々の情報を得るメモ

手法は色々出回っているがちょうど某サービスでcronジョブの途中でお亡くなりになっているプロセスがいるので便利コマンドを試しておく。

環境

php -v
PHP 5.5.8 (cli) (built: Feb  4 2014 16:57:24)

$ gdb -v
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6)

対象

2017年からご存命の6792さんを調べる

  6777  0.0  0.0 275812    52 ?        S     2017   0:00 php -q /home/xxxx/momochro.me/app/Console/cake.php -working /home/xxx/momochro.me/app update_and_notify -app /home/xxxx/momochro.me/app
  6791  0.0  0.0 273184   200 ?        S     2017   0:00  \_ /usr/local/bin/php /home/xxx/momochro.me/app/Console/do.php update_momo_items
  6792  0.0 17.4 280772 177860 ?       S     2017   0:00      \_ php -q /home/xxx/momochro.me/app/Console/cake.php -working /home/xxx/momochro.me/app update_momo_items -app /home/xxx/momochro.me/app

外から眺める

psで

$ ps -eopid,wchan:30,comm  |grep 6792
 6792 sk_wait_data                   php

sk_wait_dataで止まってる

sudo strace -p 6792
Process 6792 attached
read(10,

lsofで

$ sudo  lsof -i |grep 6792
php        6792      %USER%   10u  IPv4  937985628      0t0  TCP %HOSTNAME%:42784->202x6x245x194:https (ESTABLISHED)

202x6x245x194にestablishedになってる。 ということでhttpsなリクエストの最中に死んでそう。

gdb

phpソース直下の.gdbinitを読ませる

$ gdb -p 6792  --init-command=/usr/local/src/php-5.5.8/.gdbinit
(gdb) zbacktrace
[0x7ff32cff5860] file_get_contents("https://api.dmm.com/affiliate/v3/ItemList?api_id=xxxxxx&affiliate_id=xxx.....") /home/xxx/momochro.me/app/Lib/MomoParser/DMMItemParser.php:29
[0x7ff32cff5640] DMMItemParser->getList() /home/xxx/momochro.me/app/Console/Command/UpdateMomoItemsShell.php:41
[0x7ff32cff5498] UpdateMomoItemsShell->main() /home/xxx/momochro.me/lib/Cake/Console/Shell.php:393
...

file_get_contentsで雑にapi呼んでるところで止まってるのがわかる

$ dig +short api.dmm.com
202.6.245.194

上記をcoreを吐かせてgdb

$ gcore 6792
$ gdb /usr/local/bin/php core.6792  --init-command=/usr/local/src/php-5.5.8/.gdbinit

参考諸々

だいたいここを参照

PHPのcoredumpを読んでなぜSEGVで落ちたかを知りたい - Qiita

Robert Ames (.com!)

Amazon EchoのAlexaスキル開発で地味にはまったところをメモしておく

解決してしまえば大したことないが時間がもったいない系の色々

対話モデルの設定でサンプル発話のスロット前後にスペースがないとエラーになる問題

エラー

AddHomeShoppingIntent {Item}を追加

Error: There was a problem with your request: Parsing error in sample: AddHomeShoppingIntent {Item}を追加

以下はOK

AddHomeShoppingIntent {Item} を追加

パースエラーとしか言ってくれないし、ドキュメントのサンプルをみても特にスペースとか入ってない、のでこれをこのまま入れると怒られる。 スペース入れればおっけー。 エラーメッセージもアレだがドキュメントに書いて欲しい。

Echosim.ioで無限にsomething went Wrong. Oopsと言われる

フォーラムでのやりとりを見る限り、どうもEchosim.ioの不具合っぽい。 一応自分の環境はChromeだが特にブラウザどうこうという話でもなさそう。 再ログインして一時的に復活したりもするが一度エラーのループに入ると、「アレクサ、今日の天気を教えて」的なリクエストも全部上記のエラーとなってしまう。 このパターンはスキル側の不具合を疑っても時間の無駄なのでひとまず開発者コンソールからのリクエストで問題なければおっけーと考えた方がよさそう。 Amazon mShop appを使うと良いという回答がついているが、ようわからんので、このあたりは何かわかったら追記するやも。 amazon shoppingのアプリにalexaが搭載されており、英語で話しかける分にはテスト出来るが、こちらはamazon.comのみで英語はEngishとSpanishのみ。日本語はamazon.co.jpでしか使えず、amazon.co.jpでは音声での商品検索が出来るだけでalexaが起動するわけではないのでテストすることはやはり出来ない。

Alexa Skills Kit SDK for Node.jsでAlexa.handlerにいわゆるLambdaのcallbackを渡すとemitした段階でレスポンスを返してくれない

alexa-skills-kit-sdk-for-nodejs/response.js at master · alexa/alexa-skills-kit-sdk-for-nodejs · GitHub

このあたりの処理を見る限りcontext.succeedの形式もcallbackの形式も両対応してるようなんだけど、どうも何らかのイベントが実行され続けているらしく常にタイムアウトしてしまう。 f:id:egmc:20171128231855p:plain

AWS Solutions Architect ブログ: AWS LambdaでNode.js 4.3.2が利用可能になりました

[12/9追記]

単にメッセージを返すだけの処理でも発生するので、sdkの問題っぽい。 ローカル実行してプロファイラでおっかけたところwunderlistのsdkでした、alexa-sdkは無罪。

issueもあがってた Script Hangs · Issue #12 · wunderlist/wunderlist.js · GitHub

wunderlistsdkをnewした段階で何らかのイベントが動き続けていてタイムアウトまで実行し続けていた模様、対応は↓の通りで変わりなく、まあ必要な処理が終わった段階でのcallbackなのでcallbackWaitsForEmptyEventLoopは基本falseでもいいんじゃないかとは思う。

[/追記おわり]

回避策としてはcallbackを渡さない古いスタイルで書くか、↑の通りcontext.callbackWaitsForEmptyEventLoop = false;を指定しておけばOK。

レポジトリのサンプルではcallbackを渡しているが

https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs

ブログのチュートリアルでは渡してなかったりするのはこのあたりにはまる人が多いのやも?

Alexaスキル開発トレーニングシリーズ 第2回 対話モデルとAlexa SDK : Alexa Blogs