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 を実行すれば使える」というところまで簡単にすることで、カジュアルにデバッグをできるようにしました。

というわけで

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

undockerでdocker imageの中身を確認する

最近、同僚の @r_takaishi からの非常にありがたいPull Requestもあって、sshcの整備を行なっています。

github.com

そのときにDockerを利用してSSH接続のインテグレーションテスト環境を(ほぼ @r_takaishi が)作りました。

そして紆余曲折あって、https://hub.docker.com/r/panubo/sshd/ を使わせてもらうことに。

そして、sudoのテストを作りたくなったのですが、そのままでは設定が足りなかったので、docker run -d したpanubo/sshdのに docker exec で入って設定を調整をしてsudoのテストが書けるようになったところで、

panubo/sshdのイメージの元となっている https://github.com/panubo/docker-sshd に試した設定を追加をして docker build でイメージを作成してインテグレーションテストに利用してみました。

ところがうまくいかない

なぜかsudoに失敗するのです。

いろいろ検証して時間を溶かした結果、Docker Hubに上がっているイメージとGitHubのDockerfileに乖離がある」疑惑がでてきました。

docker imageの中身を確認する

というわけでこれまた同僚である @_tokibi が作っている undocker を使ってimageの中身を比べてみました。

github.com

tokibi/undockerは、簡単にいうとDocker imageからrootfsを取り出したり、Docker imageのメタデータを表示したりするCLIツールです。Docker imageの中身をいろいろするやつということですね。

そしてGoのライブラリとして組み込むこともできるようになっています。

今回は展開する機能を利用します。

undockerをインストール

まずはundockerをインストール

$ brew install tokibi/tap/undocker

GitHubのDockerfileのほうはbuildします

$ cd src/github.com/panubo/docker-sshd
$ docker build . -t github-image

undocker extract

次に undocker でそれぞれのrootfsを展開します

$ undocker extract panubo/sshd ./hub
$ undocker extract github-image ./github

あとはdiffで比較するだけ

$ diff -r ./hub ./github
diff -r ./hub/etc/alpine-release ./github/etc/alpine-release
1c1
< 3.6.3
---
> 3.6.5
diff -r ./hub/etc/apk/world ./github/etc/apk/world
5a6
> busybox
Only in ./hub/etc: localtime
diff -r ./hub/etc/os-release ./github/etc/os-release
3c3
< VERSION_ID=3.6.3 👈🤔
---
> VERSION_ID=3.6.5
diff -r ./hub/etc/shadow ./github/etc/shadow
1c1
< root:::0:::::
---
> root:!::0::::: 👈🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔
diff -r ./hub/etc/shadow- ./github/etc/shadow-
1c1
< root:::0:::::
---
> root:!::0:::::
diff -r ./hub/lib/apk/db/installed ./github/lib/apk/db/installed
1c1
< C:Q15chWm9+bJw8UDs+H5aW3Jw56BIg=
[...]

結論としてはrootがロックされていたことが原因でした。なんでだ??


forkして修正したDockerfileはこちら。パスワード付きsudoが通るようになっています。

github.com

WazuhでRule ID 204 "Agent event queue is flooded" が発生した際にどんなイベントが多いのかを確認する方法、あるいは視野の話

Wazuhでは各サーバにあるWazuh agent(実体はossec-agentd)(以下、エージェント)からイベントを受け取ってそのイベントを解析して様々な検知をしてくれます。

なぜ唐突にWazuhなのかについて、詳しくはこことかここをご覧ください。

ところがエージェントが取り扱うイベントが多いと "Agent event queue is flooded" つまり、バッファ溢れを起こしてしまいます。

バッファ溢れに関しては、Wazuhも公式ドキュメントにそのしくみと詳細な対策方法を書いてくれています

documentation.wazuh.com

そして、上記にある各種設定を調整してもバッファ溢れが発生する場合は、そもそもエージェントが取り扱うイベントが多すぎるということになります。

設定では厳しいので、エージェントが受け取っているイベントの見直しが必要になるのですが、エージェントが受け取っているイベントはログファイルだったりファイル更新イベントだったり多岐に渡っているので、何のイベントが多いのか特定は容易ではありません。

エージェントがどんなイベントを受け取っているのかを確認する方法

  1. まず https://github.com/wazuh/wazuh からソースコードをcloneします
  2. https://github.com/wazuh/wazuh/blob/5ebeae15e201b9e9f8d7be2216f0ebd6fc2aa98c/src/client-agent/buffer.c#L64mdebug2("Send messages to buffer: %s", msg); を差し込んておきます
  3. ビルドします
  4. 対象環境のwazuh-agentを止めます systemctl stop wazuh-agent.service
  5. ビルドした ossec-agentd を差し替えます
  6. /var/ossec/etc/local_internal_options.confagent.debug=2 を追記します
  7. wazuh-agentを起動します systemctl stop wazuh-agent.service
  8. /var/ossec/logs/ossec.log で受け取っているイベント確認します

はい、ただのprintデバッグですね

利用しているOSSをソフトウェアとみるかソースコードとみるか

エージェントのバッファ溢れはprintデバッグのあと解決に到るまではすぐだったのですが、実は、恥ずかしながら、そのただのprintデバッグにいきつくまでに非常に長い時間がかかってしまっています

そして、(確認する方法3のforkリポジトリのURLからわかるように) @pyama86 から「ビルドできるよ」としれっと示唆してもらって、初めて、やっと、printデバッグにいきつくわけです。

PHPのアプリケーションなら確実に最初にprintデバッグしてるはずなのにです。

振り返ってみると、今回私は Wazuh を「オープンなソフトウェア(OsS)」と見ていて、ソースコードがあるのに関わらず読むとか追記するという頭に最初からなっていなかったように思えました。

これを「オープンなソースコードOSs)」と見ていたら、イベントのバッファを定義している変数なり構造体なりをコードから探すでしょうし、必要な情報を出力するようにするくらいはしたはずです。

MySQLPostgreSQLOsSと見ています。Serverless FrameworkやtcpdumpOSsと見ていたなという経験があります。同じOSSなのに、自分の中での取り扱いが違うみたいです。

おそらくですが、自分のなかにOsSとみるかOSsとみるかの境目があるんだと思います。

@pyama86 はそれを「視野」と言っていました。

その視野は決して1次元で考えられるものではないでしょうし、人それぞれの過去の経験によるものなんだと思うのですが、その視野をできるかぎり「OSSソースコードとみるほう」に寄せていったほうがエンジニアとして成長できるし、意識しないと広がらないな...と感じたできごとでした。

tcpdpをPROXY protocolに対応させた

というわけで、勉強も兼ねてtcpdpをPROXY protocolに対応させてみました。

今回想定したユースケース

私自身がPROXY protocolのユースケースを持っているわけではなかったので、まずは「TCPパケットをいじらないユースケース」を想定して実装しました。

PROXY protocolを有効にして通信しているサーバの間に入ってパケットを取得する イメージです。

例えばフロントサーバにHAProxy、バックエンドにMariaDBとします。

まず、MariaDBでPROXY protocolを受け入れられるように設定します。

[mysqld]
proxy-protocol-networks=::1, 0.0.0.0/0 ,localhost

tcpdp proxy を使う場合

proxyモードの場合は以下のような構成で考えます。

+------------------------+   +-------------------------------------+
| proxy.example.internal |   | db.example.internal                 |
|                        |   |                                     |
| HAProxy:3306+---------------->tcpdp proxy:33306+--->MariaDB:3306 |
|                        |   |                                     |
+------------------------+   +-------------------------------------+

HAProxyは以下のように tcpdp が待ち受けるポートをbackendにします

global
  quiet

defaults
  mode tcp

frontend proxy
  bind *:3306
  default_backend tcpdp-proxy

backend tcpdp-proxy
  server tcpdp db.example.com:33306 send-proxy-v2

tcpdp はproxyモードで33306localhost:3306をプロキシするようにして起動します。

$ tcpdp proxy -l 0.0.0.0:33306 -r localhost:3306 -d mysql --proxy-protocol --stdout

この状態でmysqlコマンドで proxy.example.internal:3306 に接続し、クエリを発行すると以下のように proxy_protocol_src_addrmysqlコマンドを発行したIPアドレスとポートが取得できます。

{"query":"INSERT INTO t1 VALUES (NULL,1804289383,'mxvtvmC9127qJNm06sGB8R92q2j7vTiiITRDGXM9ZLzkdekbWtmXKwZ2qG1llkRw5m9DHOFilEREk3q7oce8O3BEJC0woJsm6uzFAEynLH2xCsw1KQ1lT4zg9rdxBL')","seq_num":0,"command_id":3,"conn_id":"bhjpukel0s151gfqpo1g","client_addr":"127.0.0.1:56571","proxy_listen_addr":"127.0.0.1:33306","proxy_client_addr":"127.0.0.1:56572","remote_addr":"127.0.0.1:3306","proxy_protocol_src_addr":"172.24.0.1:37948","proxy_protocol_dst_addr":"172.24.0.3:3306","character_set":"utf8","username":"root","conn_seq_num":12,"direction":"->","ts":"2019-02-16T14:27:45.318+0900"}

proxy. db. それぞれ 127.0.0.1 になっているのは Docker でローカルに構築しているからです。わかりにくいので例としてみてください

tcpdp probe を使う場合

probeモードの場合は以下のような構成で考えます。

+------------------------+   +---------------------------+
| proxy.example.internal |   | db.example.internal       |
|                        |   |                           |
| HAProxy:3306+-----------------+->MariaDB:3306          |
|                        |   |  ^                        |
+------------------------+   |  |                        |
                             | ++------------+           |
                             | | tcpdp probe |           |
                             | +-------------+           |
                             +---------------------------+

HAProxyは以下のように tcpdp が待ち受けるポートをbackendにします

global
  quiet

defaults
  mode tcp

frontend proxy
  bind *:3306
  default_backend mariadb

backend mariadb
  server db db.example.internal:3306 send-proxy-v2

tcpdp はprobeモードでネットーワークインターフェースにアタッチします

$ tcpdp probe -i eth0 -t 3306 -d mysql --proxy-protocol --stdout

この状態でmysqlコマンドで proxy.example.internal:3306 に接続し、クエリを発行すると以下のように proxy_protocol_src_addrmysqlコマンドを発行したIPアドレスとポートが取得できます。

{"ts":"2019-02-18T00:35:31.925+0900","src_addr":"127.0.0.1:53786","dst_addr":"127.0.0.1:3306","query":"INSERT INTO t1 VALUES (NULL,866596855,'naQuzhMt1IrZIJMkbLAKBNNKKK2sCknzI5uHeGAgQuDd5SLgpN0smODyc7qorTo1QaI5qLl97qmCIzl0Mds81x7TxpIoJyqlY0iEDRNKA1PS0AKEn5NhuMAr3KgEIM')","seq_num":0,"command_id":3,"interface":"lo0","probe_target_addr":"3306","conn_id":"bhknugkrtr39kl5h32ng","mss":16344,"proxy_protocol_src_addr":"172.24.0.1:49854","proxy_protocol_dst_addr":"172.24.0.2:3306","character_set":"utf8","username":"root","database":"mysqlslap"}

こちらも同様にわかりにくいので例としてみてください

今回想定しなかったユースケース

今回想定しなかったユースケースとして、

PROXY protocolに対応していないRDBMSの前段に tcpdp proxy でプロキシサーバをたてて、upstreamのRDBMSにはPROXY protocolヘッダを外したパケットを流す

というものもありました。

これを実装するとMySQLPostgreSQLでもPROXY protocolで接続元の情報を取得できるようになります。 (今回MariaDBを採用した理由もPROXY protocolに対応しているからでした)

ただ、そこまで必要なのかがわからなかったので、パケットをいじらない実装をしました。

こちらも要望があれば検討しようと思っています。

というわけで

休日の空いた時間にチマチマと進めていたのですが、PROXY protocol、勉強になりました!

あと、今回はじめてHAProxyを触ったのですが便利ですねコレ。これも良い経験になりました。

ディレクトリを遡ってMakefileを探してそのディレクトリでmakeを実行するmkを作った

Goを書くようになってからなのか、GMOペパボに入ってからなのか、make を実行することが多くなったのですが、makeMakefileがあるディレクトリで実行する必要があります(もしくは -C オプションでMakefileのあるディレクトリを指定)。

ただ、rakenpm に慣れてしまっているからか、ディレクトリ関係なく make を実行してしまい *1、そのたびに make: *** No targets specified and no makefile found. Stop. を突きつけられてしまっていました。

というわけで、ディレクトリを遡ってMakefileを探してそのディレクトリで make を実行するだけのコマンド mk を作りました。

github.com

インストール

go get か Homebrew でインストールできます。

$ go get github.com/k1LoW/mk

or

$ brew install k1LoW/tap/mk

使い方

make の代わりに mk を使うだけです。

$ cat Makefile
hello:
        @echo "Hello make"
$ cd path/to/deep/
$ cat Makeflie
cat: Makefile: No such file or directory

上記のようにMakefileのあるディレクトリでないところで、make を実行してもエラーになりますが

$ make hello
make: *** No targets specified and no makefile found.  Stop.

mk だとディレクトリを遡ってMakefileを探して make を実行できます。

$ mk hello
(in /path/to/deep)
Hello make

alias make='mk' として使ってみようと思います。

*1:rakeやnpmは Rakefileやpackage.jsonディレクトリを遡って探してくれます