この記事ははてなエンジニアアドベントカレンダー2014の8日目です。
今回は、Go言語でサーバ管理ツール Mackerel のコマンドラインツールmkr
を作るときに調べたこと、考えたこと、やったことについて紹介します。(mkr
は現時点では開発版での提供になります。)
コマンドラインツールについて
コマンドラインツールを作るにあたって、@deeeet さんの YAPC Asia 2014 での発表資料が非常に参考になります。 書籍 UNIXという考え方ーその思想と哲学 の内容をベースに、コマンドラインツールはどうあるべきかということが丁寧に説明されています。
上記資料から引用させていただくと、コマンドラインツールにおいて重要なポイントは以下の7つであるとされています。
- 1つのことに集中している
- 直感的に使える
- 他のツールと連携できる
- 利用を助けてくれる
- 適切なデフォルト値を持ち設定もできる
- 苦痛なくインストールできる
- すぐに改修できる
また、「いかに"良い"CLIツールを作り始めるか?」について、以下の4つの手順が示されています。
- どんなツールを作るか考える
- 言語を選ぶ
- README.mdを書く
- 高速にプロトタイプを作る
さらに、同じく@deeeet さんのエントリ コマンドラインツールを作るときに参考にしている資料 | SOTA も参考になります。
Go言語におけるコマンドラインツール
Go言語は依存のないバイナリ生成とクロスコンパイルができるので、"苦痛なくインストールできる"という点で、コマンドラインツールを書くことに向いていると思います。
Goで書くとRubyなどと比べて基本的には動作が高速なため、Heroku や GitHub のコマンドラインツールがGoで書きなおされているというのも注目すべきポイントです。
hk
は使ってないのでわかりませんが、hub
は体感でもはっきりわかるほど動作が高速になりました。
- heroku/hk · GitHub
- github/hub · GitHub
- jingweno/gh · GitHub
- motemen/ghq · GitHub
- https://github.com/tcnksm/dmux
- walter-cd/walter · GitHub
余談ですが、Heroku ではGoでコマンドラインツールを書く仕事を募集していたようです。 Go/Golang job: Command Line Interface Developer @ Heroku
Go でコマンドラインツールを作る上で、コマンドラインツールのインタフェース部分の実装を助けてくれるcli
というライブラリが非常に便利です。
これを使うとサブコマンドをもつインタフェースや、ヘルプ機能などを簡単に作ることができます。(Ruby でいうとこるの thor のライブラリ部分に近いかも)
使い方は motemen さんの ghq や cli
の作者のGoのコードをみるとよいと思います。
またコマンドラインツールとは関係ないですが、Go でのよいコードの書き方について、先日のGoConの資料が参考になります。 Goに入ってはGoに従え
Go については、以前便利リンク集をまとめました。 Go言語の便利情報 - ゆううきブログ
mkr
mkr
は、サーバ管理ツール Mackerel の REST API を利用したコマンドラインツールです。
Mackerel のような管理ツール系のサービスは、ある操作を一括で行いたいなどの柔軟な操作性を求められますが、それをWebUIで表現することが難しいこともあると思います。 そんなときに、APIによる一括操作などができると、操作が自動化しやすいので一石二鳥といえます。 特にサーバ管理ツールの場合、他のツールのAPIなどの出力をサーバ管理ツールに反映したり、逆にサーバ管理ツールのAPIの出力を他のツールに渡したいなどの要求はよくあると思います。
例えば、Mackerel API と tssh を組み合わせて複数ホストに同時にログインするなどの応用があります。tmux + ssh + Mackerel API を組み合わせたとにかくモダンなサーバオペレーション - ゆううきブログ
サーバ管理ツールとAPIについては、昨年のYAPCで発表した資料が参考になるかもしれません。
はてなのサーバ管理ツールの話 // Speaker Deck
mkr
は各種APIの操作とコマンドが対応しており、APIの入出力とコマンドラインでの入出力のパイプとしてだけ機能するように意識しています。
"1つのことに集中する"ことができるように、出力のフィルタリング/加工機能などは最小限の実装にしています。
代わりに、APIの出力を余分な情報を多少除いてそのままJSONで出力するようにして、jq
で自在にフィルタリング/加工すれば"他のツールと連携できる"ことがしやすいと考えました。
例えば、特定ロールのホスト群のIPアドレス一覧を出力したいときには mkr の hosts サブコマンドに service と role オプションを付けて、jq で eth0 のみをフィルターします。
$ mkr hosts --service Mackerel --role proxy | jq -r -M ".[].ipAddresses.eth0"
同等のフィルタリングを mkr
側もしくはAPI側で実装しようと思うとかなり大変で、実装したとしても表現力が汎用フィルターの jq
に劣る可能性が高いため、あらゆるツールと組み合わせるというわけにはいかなくなるかもしれません。
他のサービスのAPIと組み合わせとして例えば、EC2 と Mackerel 連携があります。
mkr retire <hostIds>
と aws-cli
の descrive-instancesを組み合わせると、EC2上で Terminated になったインスタンスリストを mkr retire
コマンドに渡せば、一括で退役処理ができます。
さらに、mackerel-agent-plugins
と mkr throw
を組み合わせると、Sensu 形式のプラグインの出力をホストメトリックやサービスメトリックに投げ込めます。
cron で定期投稿させることで、mackerel-agentがなくてもメトリック投稿ができます。
ELB や RDS など、特定のインスタンスに紐付かないメトリックはこれを使うと楽かもしれません。
$ /usr/local/bin/mackerel-plugin-aws-elb | mkr throw --service <hostId>
mkr と Go
mkr
を Go で実装した最大の理由は、mackerel-agent がインストールされたホスト上で、mkr status
とうつとログインするホストの情報を簡単にみたり、ステータスを変更できるようにしたいというものです。
mackerel-agent が動作していれば、/etc/mackerel-agent/mackerel-agent.conf
や /var/lib/mackerel-agent/id
にAPIキーやホストIDが書かれています。
指定がなければこれらを読むようにすることで、入力を省略できるようにします。
各ホストにインストールされている必要があるので、配布の簡単な Go で実装するのが適当だと思いました。
mkr 作成手順
下記の手順で作りました。
- インタフェースを決める
- READMEを書く
- go 版のAPIライブラリがなかったので作る https://github.com/mackerelio/mackerel-client-go
- ひな形を作る https://github.com/tcnksm/cli-init 高速にGo言語のCLIツールをつくるcli-initというツールをつくった | SOTA
ghq
を参考に実装。とにかく参考になる。go vet
,golint
を通す- CI環境を整える (TravisCI)
- リリースフローを整える (goxc, travisci)
- Dockerfile を書いて、DockerHub で Automated Build https://registry.hub.docker.com/u/mackerel/mkr/
- Homebrew Formulaを書く https://github.com/y-uuki/homebrew-mkr (これも motemen さんの https://github.com/motemen/homebrew-ghq をそのまま)
インタフェース
mkr
はインタフェースにこだわって作っています。
例えば、Ruby のCLIツールのインタフェースは https://github.com/mackerelio/mackerel-client-ruby#cli のようになっていますが、コマンド名 + リソース名 + 動詞 となっており、REST をそのまま表現しやすいですが、タイプ数が多くなってしまうという欠点があります。
(一応サブサブコマンド的なものもcli
ライブラリで実装できるようです。)
そこで、mkr
はコマンド名 + 動詞で表現するようにして、タイプ数が短くなるようにしています。
代わりに操作したいリソースの表現力が落ちますが、実際には"service"(リソース)を"create"(動詞)するなどの操作は通常コマンドラインからは行いません。
このようにWebUIで十分事足りる操作については、コマンドラインでは表現しないもしくはタイプ数をあまり気にしないインタフェースにしています。
頻繁にコマンドラインで操作したいと思うのは"host"であるため、基本的には"host"(リソース)に対する操作を動詞としています。
また、APIでは異なるエンドポイントとして設計されていても、ユーザの直感では1コマンドとして表現されていたいと思うものもあります。
例えばAPIでは、ホスト情報の更新とホストのステータスの更新が分かれていますが、どちらもホスト情報の更新であるため、mkr
では update コマンドにまとめてあります。
リリースフロー
今は GitHub の releasesにアップロードすることをリリースとしています。 "苦痛なくインストールできる"ことを考えると、yumリポジトリやaptリポジトリ、Homebrewで提供することを考えたほうがよりよいとは思います。
CI環境と合わせて GitHub の releases へのアップロードは TravisCI, CircleCI、wercker を使うと便利です。
Travis CI: GitHub Releases Uploading
さらに、複数環境の同時クロスコンパイルには gox
または goxc
が便利です。
toolchain の細かい validation や Zip や tar.gz へのアーカイブ、バージョニングなどができる点から goxc
を使ってみました。
このあたりは、Makefileやtravis.ymlを参照してください。
Dockerfile
モダン感を出すために、とりあえず置いておくとよいでしょう。 2行で済みます。(ビルドに make を使えなかったりするので、ちゃんとやろうとすると onbuild image 使わずにやるけどとりあえず動く)
FROM golang:1.3.3-onbuild ENTRYPOINT ["/go/bin/app"]
Go でないツールで docker さえ入っていればコマンド一発でインストールできるので、Ruby や Python のコマンドラインツールでは有用かもしれません。
まとめ
UNIX哲学最高。Go最高。
- 作者: Mike Gancarz,芳尾桂
- 出版社/メーカー: オーム社
- 発売日: 2001/02
- メディア: 単行本
- 購入: 40人 クリック: 498回
- この商品を含むブログ (140件) を見る
次回は id:hatz48 さんです。よろしくお願いします!
はてなでは、Perlだけでなく、Scala, Goのエンジニア、さらに自社開発のツールでサーバ管理がしたいWebオペレーションエンジニアも募集しております。
採用情報 - 株式会社はてな