技芸のコードによるボトムアップ #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 を実行すれば使える」というところまで簡単にすることで、カジュアルにデバッグをできるようにしました。

というわけで

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

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 start 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を触ったのですが便利ですねコレ。これも良い経験になりました。