google/gopacket + libpcap で作るツールのパケットバッファ戦略

tcpdpの中のお話です

パケットを一時的に保持するバッファ

google/gopacket/pcap パッケージでパケットを取得するようなツールを作る場合、2つのバッファを持つことになります。

  1. libpcapが持つバッファ
  2. google/gopacketが持つバッファ(バッファ付きチャネル)。1000で固定。

パケットは加工されつつ上記のバッファを経由して、最後にgopacketからパケットを受けとることになります。

そしてバッファには、以下のような特徴があります。

  • libpcapが持つバッファが溢れたらパケットロスが発生します
  • gopacketのバッファサイズは固定です
  • libpcapは ImmediateMode がOFFだと、libpcapが持つバッファにある程度パケットを貯めてからまとめて返します

上記特徴からみても、ツールが正しく動くようにするためには いかにlibpcapのバッファを溢れさせないか が重要になってきます。

パケットロスを回避する戦略

そして、取れる戦略(とその戦略をとった場合のデメリット)は2つです。

  1. libpcapが持つバッファを大きくする ( bufferSize の調整 )
    • デメリット: bufferSize の分、最初に確保されるメモリ量が大きくなる
  2. libpcapからできるだけ早くパケットを回収する ( 例えば、immediateMode の有効化
    • デメリット: immediateMode が無効状態よりもCPUリソースを消費する

例えば tcpdp の場合、gopacketからパケット取得した後の、「パケットを加工して構造化ログに吐き出す機構」がボトルネックになります。まあ、そうですよね。

つまり、libpcap -> gopacket -> tcpdp の最後の処理に一番時間がかかります。パイプラインの最後の出口が詰まってしまう感じです。

これはもうどうしようもなさそうではあるのですが、もし

  • パケットの流量に波がある
  • パケットのピーク流量が一定時間発生しかなくて、それさえ耐えれば、以降の通常のパケット処理は十分処理可能である

と予想されるのであれば、「ツール内にlibpcap、gopacketに続く3つ目のバッファを持って、そのバッファに処理待ちパケットを溜め込む」という選択肢が取れます。 つまり戦略2です。

f:id:k1LoW:20190813115633p:plain

tcpdpだと internalBufferLength というパラメータで指定するのですが、これは単なるバッファ付きチャネルです。

図で言うとlibpcapからガンガンパケットを回収して、早々にtcpdpのバッファに溜めてしまいます。

そして、そのバッファに溜まったパケットを逐次処理していきます。tcpdpのバッファが溢れるまではパケットロスが生まれないという仕組みです。

まとめ

google/gopacket + libpcapで作るパケットのバッファリングの仕組みと、パケットロスを発生させないようにするバッファ戦略について紹介しました。

正直、結構戦略としては苦しいものなのですが、tcpdpだとパケットはシーケンシャルに処理しないといけなかったりするのでいまのところこのような実装になっています。

何かもっと良い方法があれば教えてください。

tail -F /path/to/access.log のようなストリームに対して何度でもgrepを試行錯誤できるツール filt を作った

filtというツールを作りました

github.com

これはなに

まずは以下のスクリーンキャストをご覧ください

f:id:k1LoW:20190730230540g:plain

tail -F /path/to/access.log のようなログの流れを止めてはgrepをして、止めてはgrepのコマンドを変更してしています。

その間、実は裏では実際のストリームは止めていません。

つまり、ストリームのパイプ先に まずfiltを指定しておく ことで、ストリーム自体は止めずに、パイプ先のコマンドを 後で何度でも 変更できます。

grepsedawkが下手なので何度も試行したい

私はgrepsedawkなどのコマンドを一発で指定できるほど練度が高くないので、コマンドを組み立てるために何度も Ctrl+C でキャンセルしては再実行の繰り返しをしていました。

最近は sternHarvest などでKubernetesのPodのログを流すことをもあるのですが、ログが流れ出すまでちょっと時間がかかります。それに対して毎回キャンセルして再度コマンド組み直しというのは結構ストレスです。

filt を使ってこの試行錯誤の敷居を下げることで、手早くコマンドを組み立てられるし、カジュアルにコマンドを組み立て直すこともできます。

そして、「まずはログを流しておく」「いろいろフィルタしてログをみる」というログを見る良い習慣にもつながると考えました。

使い方

MacでのインストールはHomebrewが利用可能です。

$ brew install k1LoW/tap/filt

filt コマンドが使えるようになったら、あとは、tail -F などのストリームにパイプでつなげるだけです。

$ tail -F /var/log/nginx/access.log | filt

上記のように直接パイプで filt を指定するのがオススメです。

流れているログに対して「grepでフィルタリングしたいな」と思ったら EnterCtrl+C を押します。すると、ログの流れが止まりプロンプトが出て入力待ち状態にになります。

そのプロンプトに grep -v GET などのコマンドを入れて Enter を押すと、今度は tail -F /var/log/nginx/access.log | grep -v GET でフィルタリングされたログをが流れます。

もう一度フィルタするコマンド入れ替えたいときは、またEnterCtrl+C を押すことでログの流れが止まりプロンプトが出てきます。

これの繰り返しです。

tail -F なストリーム自体を終了したいときはプロンプトが出ているときに exit と入力して Enter を押すか、 Ctrl+C を押してください。

filtを作ってから

私が「まずはログを流しておく」ようになりました。

Ctrl+C を押したらまた最初からログが流れてくるのを待たないといけないのか」というような心理的な障壁もないので、カジュアルにコマンドを組み立てなおして、「今欲しい情報だけが流す」ようになりました(それまでは目で一生懸命眺めることがあった)。

以前作ったevryなども指定できます。

k1low.hatenablog.com

結構、開発体験が変わるのでおすすめです。是非使ってみてください。

Songmu/timeoutからコマンドの停止処理部分を理解しつつ切り出してk1LoW/execを作っている #gocon

Go Conference'19 Summer in Fukuoka、私の中でもう少し続いています。

exec.CommandContext は "孫プロセスがあった場合にそれが止まらない"

songmuさんの発表は聞きたいなと思っていたのですが、40分間これでもかと知見が込められた発表でした。ツール作るマンとしてはいろいろ心に留めておきたいことばかりでした。

その中でも「コマンドを停止する」の章は、実はスライドレベルでいうと2回目なのですが(ヒント: Go Conference 2019 Spring)、やはり直接聞くと「ごめんなさいごめんなさい」という気持ちになりました。

というのも、私はよく以下のように exec.CommandContext を書くのですが、

cmd := exec.CommandContext(ctx, "bash", "-c", commandStr)

ctxのキャンセルに任せてしてしまって、孫プロセスになるcommandStrを放置している。。。

GoConでコマンドの停止処理について、スライド資料とともにコードレベルでしっかりと解説してもらったので、これはしっかりと書いていこうと思いました。

とはいえ、いろんなところに exec.CommandContext をばらまいてしまっている。。。

k1LoW/exec

というわけで、勉強と実益を兼ねて、os/execと同じ書き方ができつつも、孫プロセスを見捨てないk1LoW/execを作りはじめています。

github.com

使い方はos/execと全く同じです。os/execにinterfeceがあるわけではありませんが、os/execと同じメソッドは全て用意しているので、そのまま切り替えて使えます。

import (
    // "os/exec"
    "github.com/k1LoW/exec"
)

また、k1LoW/exec.Command の返り値も *os/exec.Cmd のままです。

ただ、さきほどと同じように書いても、

cmd := exec.CommandContext(ctx, "bash", "-c", commandStr)

ctxがキャンセルした時には孫プロセスのcommandStrにもちゃんとシグナルが渡ります。

つまり os/execgithub.com/k1LoW/execに差し替えれば解決!! というのを目指しています。

import "github.com/Songmu/timeout"

k1LoW/execのコマンド停止処理部分は、発表で学んだ内容 = Songmu/timeoutのコマンド停止処理部分が ほぼそのまま です。実質Songmu/timeoutをライブラリとしてimportしている感じです。

それでも書いて良かった点は、テストコードを自分なりに書いてos/execとの違いを確認したことです。

os/exec.CommandContext ではFAILになるのに、k1LoW/exec.CommandContext ではSUCCESSになるというのは「なるほどー!!」となる瞬間でした。これは発表を聞いただけの理解の時とは違いました。具体的に手を動かして良かったです。

これから

k1LoW/execでは、あと以下の2つを実現したいと思っています。

Windowsサポート

Songmu/timeoutWindowsもしっかりサポートしており、かつテストもとてもわかりやすく書かれています。これを参考にWindowsのコマンド停止処理までしっかりと移植しつつ、WindowsのテストやCIの書き方を学んでいきます。

KillAfter/KillAfterCancelのサポート

KillAfter/KillAfterCancelSongmu/timeoutにある実装です。 コマンドの停止処理のお作法であるSIGTERM -> SIGKILLの流れ を、出来るだけ今のos/execなインターフェースを崩さずに実装しようと思っています。こちらは、あとは書くだけかなと思っています。

というわけで

songmuさん、知見のつまった発表ありがとうございました。ぜひ、またお会いしましたらよろしくお願いします。

残りの実装も書いていきます。

技芸のコードによるボトムアップ #phpgenba #phpconfuk #gocon

最近やっていることの記録です。

技芸のコードによるボトムアップ

「技芸のコードによるボトムアップ」「技芸のコード化」というのは、端的にいうと自分の「サーバオペレーション」の能力を、自分の得意な分野である「コード化」の側面からボトムアップしていこうという取り組みです。

この取り組みは先進的ではないのですが、これには少なからず意味があると思っていて、それはサーバオペレーションというある種の速度や経験が必要な領域を、再現可能なコードで一部でもカバーできれば、それだけで人をまたいだ全体の作業効率が向上すると考えているからです。

「サーバオペレーションの速度も経験もない。なら、コードが書けるんだから、コードで差を埋めていく」

というのが「技芸のコードによるボトムアップ」という言葉に込めた気持ちです。

ここらへんの話を最初に外でしたのは @shin1x1 さんのポッドキャストPHPの現場」に呼んでいただいた時だったと思います。

php-genba.shin1x1.com

他の話も転職してから約1年のタイミングで、話すことで自分としても現状を整理できたと感じています。 あと、 @shin1x1 さんの安心感がすごい。

本当に良い機会をもらいました。ありがとうございました。

ログオペレーション

ログ、特に生ログについては、前からうまく扱えないかと考えています。というのもやはりサーバオペレーションの能力に大きく関係するからです。

例えば障害発生時の初動に役に立つのはアラートやメトリクスだと思うのですが、最後の原因特定からの解決などにはやはりログが重要な部分を占めると思っています。

「適切なログファイルを見つけて、そのログから有用な情報を読み解く能力」というのは、有事でも平時でも非常に強力な能力なので、この部分をボトムアップできないかというのは前から考えていました。

当時の構想などはWSA研で発表していたりします。

k1low.hatenablog.com

上記は「ログファイルを見つける」側のアプローチです。

このWSA研で @hirolovesbeer さんと Hayabusa に出会い、そこから実装のアプローチを参考にして現在作っているのが Harvest です。

github.com

Harvestは、スキーマを定義しておくことで、サーバへの事前インストールなしに複数のサーバのログを1つに集約し、時系列に保存したりリアルタイムにtailしたりするツールです。SSHでログインできるサーバ/インスタンスやローカルのログだけではなくKubernetesのPodのログも透過的に取得可能です。

Harvestには、さらにWSA研で発表した構想からアップデートしたものを機能として実装したいと考えています。「tag DAG」と呼んでいるのですが、これについてはまだ足りないピースをどうしようか考えながら寝かせています。

「ログから有用な情報を読み解く」側のアプローチでは、「例えばログ内の数値情報などは検出しやすいし、特徴量を取得しやすいのではないか?」とトークンナイザを作ってみたりして、次のアイデアを思いつくまでこれもまた寝かせています(そしてそのアイデアは残念ながらまだ生まれてきません)。

github.com

PHPカンファレンス福岡2019

では、現実の現場はどうなのか?ということで、ログオペレーションとそれへのコードによるアプローチについて私が経験したことを、PHPカンファレンス福岡2019で発表させてもらいました。

資料では色々書いていますが、今時点での私の感想は「とはいえ、やはり私自身の練度が足りない」という残念な感想なのですが。これは頑張ります。

PHPカンファレンス福岡2019自体は、またレベルがあがっていてすごいと思いました。安定感があって楽しく過ごせました。本編前後の非公式コンテンツも楽しい。

ただ、私にとっては今回は「PHPコードチャレンジ -マネクラからの挑戦状-」の年でした。

note.mu

チームとしても結構なコストをかけて開催しました。当日もほぼかかりっきりでしたが、楽しんでもらえているのが目に見えて嬉しかったです。

内輪の話ですが、脆弱性修正チャレンジを「やりますよー」と、すっと問題を作ってくれるセキュリティ対策室の @mrtc0 すごい。

あと、Webサイトコードゴルフ、もうちょっと流行らせたい。

Go Conference'19 Summer in Fukuoka

GoCon Fukuokaではログ関連で作ったツールの、特にデータ収集処理に着目してそれぞれのアーキテクチャをパイプラインの実装という文脈で紹介しました。

図とデモとコードを混ぜて説明したのは概ね好評だったみたいで、ほっとしました(結構しゃべりを入れて説明したので資料ではわかりにくいかも)。

スライドでユースケースとして紹介した各ツールについてはまた別途エントリを書きたいと思います。

カンファレンス自体は、少人数のスタッフで回していたらしいのですが、そんなことを感じることなくノーストレスで楽しめて、最後のスタッフ集合まで少人数であることに気づかないくらいでした。あと、生ハムとビールがカンファレンス途中に出てくるのはズルい。自分の発表が終わるまで飲めなかった。。

Goならではのコミュニティの雰囲気を感じることができて楽しかったです。

これから

「技芸のコードによるボトムアップ」というアプローチは、対象をサーバオペレーションに絞っているだけであって、基本的にはコードによる汎用化・効率化の領域だと思っています。

少しづつですが、効果がでているのでまだ続けていこうと思います。

それとは別に、そもそも私のサーバオペレーションの練度が足りない点については、精進しようと思います。。。

tblsがデータソースとしてBigQuery / Amazon Redshiftに、出力フォーマットとしてxlsx / PlantUMLに対応した etc.


tbls

前回のエントリからtblsにいくつか機能追加をしたので、v.1.15.2時点での現状報告です。

BigQueryを bq:// でサポート

データセット単位でドキュメント化できるようになりました。

認証情報として環境変数 GOOGLE_APPLICATION_CREDENTIALS を設定するか、以下のようにDSNのクエリにJSONファイルを指定してください。

$ tbls doc bigquery://project-id/dataset-id?creds=/path/to/google_application_credentials.json

以下は bigquery-public-data:bitcoin_blockchain をドキュメント化したサンプルです。

tbls/sample/bigquery_bitcoin_blockchain at master · k1LoW/tbls · GitHub

Amazon Redshiftを rs:// でサポート

もともとtblsでもpostgres:// の指定で、Amazon Redshiftでもドキュメントを出力していたようなのですが、機能追加の結果長らく壊れていたようでした。

そこでスキームを redshift:// に分離してサポートすることにしました。

ただ、いまだにAmazon Redshiftの検証環境を用意できていないので、 watarukura さんのサポートを受けてトライアンドエラーで対応しました。

github.com

ありがとうございます!

Excel出力に対応

外部の人にスキーマ情報を共有するのに、ポータブルなExcelファイルでの出力が欲しくなってしまい、つい作ってしまいました。

以下のように tbls out コマンドで出力可能です。

$ tbls out mysql://dbuser:dbpass@hostname:3306/dbname -t xlsx -o schema.xlsx

PlantUMLフォーマットでの出力に対応

今週末はちょっと疲れていたので、数時間で完全理解!わりとゴツいKubernetesハンズオン!!と簡単なOSSメンテナンス業(holiday_jp-* の修正とか)とこれをやっていました。

Excelと同様に tbls out コマンドで出力可能です。

$ tbls out mysql://dbuser:dbpass@hostname:3306/dbname -t plantuml -o schema.puml

PlantUMLのオンラインサーバ で出力したのが以下の画像です。

f:id:k1LoW:20190526231631p:plain

PlantUMLって、私は使ってこなかったのですが、!define での関数定義などできて面白いですね。

RedmineプラグインがあったりChromeエクステンションがあったりと、結構使われているみたいなので誰かに刺さる機能だったらいいなと思います。

ドキュメントの充実

tbls doc 以外の機能も増えてきたので、READMEを充実させてみました。

tbls/README.md at master · k1LoW/tbls · GitHub

tbls lint という データベーススキーマとドキュメントにLintをかける という便利機能もあったりするのですが、これはまた別のエントリで紹介したいと思います。

ロゴができました

今回は自分で作らずに依頼してみました。気に入っています。

というわけで

Qiitaでもtblsの便利Tipsや便利ツールの紹介があったりして嬉しい限りです(まだ半分は私のエントリですが)。

qiita.com

ぜひ使ってみてください。

もし「ちょっと試してみようかな」と思った方は

あなたが使っているデータベースに接続できる環境で以下のコマンドを試してみてください。 テンポラリに tbls コマンドが使えるようになります。

$ source <(curl -sL https://git.io/use-tbls)
You can use `tbls` command in this session.
$ tbls doc my://root:mypass@localhost:33308/testdb 👈あなたのデータベースのDSNを指定してください

内閣府が提供するsyukujitsu.csvが1955年からの祝日情報を提供するようになっていた

またしても syukujitsu.csv の中身が変更になりました。

変更点は私調べによると以下の2つです。

  1. 1955年からの祝日情報を提供するようになった
  2. 振替休日などの表記が 国民の休日 から 休日 に変更になった

過去の祝日について、国から確かな情報が手に入るようになったので便利ですね。

holiday-jp/holiday_jp

  • 1についてはUNIX時間の関係もあるのと、あまりメリットがないので1970年より前の祝日については追従しないでおこうと思います
  • 2については追従予定です

という形でPull Requestまでは作成しています。

Follow syukujitsu.csv by k1LoW · Pull Request #106 · holiday-jp/holiday_jp · GitHub

ただ、どうやら過去の祝日提供をした結果、一部syukujitsu.csv側に間違えがあるっぽいことがわかったので、また内閣府に連絡してみようと思います

後日

プロセスやホストのメトリクスをトリガーに任意のコマンドを実行できるデバッグ用ツール Sheer Heart Attack を作った

サーバの運用をしていると、

  • なぜかホストのCPU使用率やメモリ使用率が上昇するタイミングがあるが、なぜ(何が)そのタイミングで上昇しているのかわからない
  • CPU使用率やメモリ使用率を通常時以上に使用しているプロセスにはあたりがついているが、なぜそのプロセスがそのタイミングでそのような使用率になっているのかわからない

というような状況にでくわすことがあります。

そして、そこから原因を調査していくのですが、今回、上記のような状況のデバッグに活用できるツール "Sheer Heart Attack" を作りました。

github.com

これはなに

指定のプロセスやメトリクスを一定間隔でトラッキングし、閾値を超えた時点で任意のコマンドを実行するバッググラウンドプロセス(以下、trackプロセス)を生成してくれるツールです。

例えば

ホストのCPU使用率が80%以上になった時の ps auxf の結果を確認したい

あるPIDのプロセスが開いているファイルディスクリプタの数が100を超えた時の strace の結果を取得したい

などの用途に活用できます。

trackプロセスはメトリクスのトラッキングやコマンド実行のほかに、

  • コマンドの実行結果(STDOUT, STDERR)を構造化ログで記録する
  • 指定の回数コマンドを実行したら終了する
  • 指定の時間が経過したら終了する
  • イベントごとにSlack通知をする

といった機能を持ち、カジュアルにプロセスやホストのメトリクスをトリガーとしたデバッグに活用できるようになっています。

使い方

一時的なインストールをするためのスクリプトを用意しているので、それを利用します。

root@kilr_q:~# source <(curl -sL https://git.io/sheer-heart-attack)
You can use `sheer-heart-attack` command in this session.

そして sheer-heart-attack launch コマンドを実行します。

root@kilr_q:~# sheer-heart-attack launch

あとは、対話的に必要な設定を入力するだけで、trackプロセスが実行されます。

f:id:k1LoW:20190415012829g:plain

実行されたtrackプロセスはバックグラウンドプロセスなので、 そのサーバからログアウトしてもメトリクスをトラッキングし続け、閾値を超えたタイミングで指定のコマンドを実行します

設定するオプションや取得可能なメトリクスはREADMEに記載していますので、そちらをご覧ください。

なぜ作ったのか

実は、 @hiboma のProcDumpのエントリを読んでいて「ProcDumpみたいな汎用的なツールがあれば便利かも」と思っていたのでした。

hiboma.hatenadiary.jp

そして、まさに冒頭に書いたような状況になり 、チームメンバと「あったらデバッグが楽そう」と話すことがあったので作成してみました。

デバッグツールとして作り込む

デバッグツールはいつも使うわけではありません。

なので、Sheer Heart Attackは

  • 事前にインストールしている状況を求めない
  • 使い方の事前把握は最小限にする

ということを念頭に置いて設計しました。

事前にインストールしている状況を求めない

これは簡単インストールのために source <(curl -sL https://git.io/sheer-heart-attack) のコマンドを用意することで実現しています。

上記URLの実体はシェルスクリプトで、mktemp -d で作成したテンプディレクトリに最新のsheer-heart-attackをインストールして、source でPATHを通す動きをします。

これは、GoReleaserのCI上でのインストール方法を参考にしました。

実は、Sheer Heart Attack以前に、tblsで同じようなスクリプト用意していたりします(こちらはCI用途)。

使い方の事前把握は最小限にする

オプション無しの sheer-heart-attack launch を実行するだけで利用できるようにしています。

また、必要なオプションは対話的に入力可能できるようにして、オプションを覚える必要がないようにしています(慣れてきた人用に --non-interactive オプションも用意しています)。

sheer-heart-attack launch を実行すれば使える」というところまで簡単にすることで、カジュアルにデバッグをできるようにしました。

というわけで

カジュアルに使えるので是非使ってみてください。