ホストにログインしてから Ruby on Rails のアプリケーションで rake
を実行するまでの前準備が長すぎるので、 appdo
という一つのコマンドにまとめてしまおう、という話をします。
解決したい問題
Ruby を rbenv や rvm で入れていると、 sudo
や cron
等でのコマンド実行時に ruby
が見つからなくて困ります。まあ ~/.bashrc
などを都度読めばいいんですが、そうするとコマンドが複雑になってしまいます。そういう処理をしてる箇所が増えると、修正漏れも起きやすくなります。こまる。
Ruby on Rails のアプリケーションを運用しているときにはさらに cd $RAILS_ROOT
しないと bundle exec rake
も bin/rails runner
も実行できません。結果、運用コマンドはどんどん長くなり、 crontab
や config/deploy.rb
はメンテナンスしづらくなってしまいます。こまる。
どうせ同じホストではいっつもおんなじ準備をするんだから、そんな設定はホストに持つべきでしょーってことで、そういうコマンドを作りました。
appdo
で解決だ
アプリのコンテキストでコマンド実行するための appdo
コマンドというものを作りました。pip install
できます。
pip install appdo
appdo
を使うと、これまで以下のように実行していた rails c
が…、
$ su - web # まずログインして…
$ cd my-awesome-app # あった、このディレクトリだ
$ cd current # あ、capistrano 管理下だった
$ export RAILS_ENV=production # 環境変数設定してーの
$ export RACK_ENV=production # (なんかいろいろ)設定してーの
$ bundle exec rails c # ほい
これだけで済むようになります。
$ sudo -iu web appdo rails c # webユーザのアプリ内で rails c
設定ファイルは以下のように、 $HOME/.appdo.conf
に書いておきます。書式は TOML。
# /home/web/.appdo.conf
#
## sudo した場合、 sudo 先のユーザの .appdo.conf が適用されます
[default]
cd = "~/my-awesome-app/current"
source = ["/etc/profile", "~/.bashrc"]
prefix = "bundle exec"
[default.env]
RAILS_ENV = production
RACK_ENV = production
見たまんま、 cd
して source
して環境変数設定したうえでコマンドを bundle exec
してくれるわけです。
必ず毎回やることなら、人間がやるべき仕事じゃないですよね。設定ファイルに入れ、そのファイルをプロビジョニング管理する方が人間が幸せになります。
その他の変化
crontab
の変化
appdo がないとき
RAILS_ROOT=/home/web/my-awesome-app/current
BASHRC=/home/web/.bashrc
RAILS_ENV=production
30 3 * * * bash -c "cd $RAILS_ROOT; source $BASHRC; bundle exec rails runner MyAwesomeApp::MyCron.run()"
30 4 * * * bash -c "cd $RAILS_ROOT; source $BASHRC; bundle exec rails runner MyAwesomeApp::MyCron2.run()"
30 5 * * * bash -c "cd $RAILS_ROOT; source $BASHRC; bundle exec rails runner MyAwesomeApp::MyCron3.run()"
appdo があるとき
30 3 * * * appdo rails runner MyAwesomeApp::MyCron.run()
30 4 * * * appdo rails runner MyAwesomeApp::MyCron2.run()
30 5 * * * appdo rails runner MyAwesomeApp::MyCron3.run()
環境変数を設定する必要がない(appdoが吸収してくれる)ので、crontab が超シンプルになります。
何のコマンドを打ってるのか、appdo なら見えるよ。わたしにもみえるよ
ssh でのコマンド変化
appdo がないとき
ssh my.server.example.com sudo -u web bash -c "cd /home/web/my-awesome-app/current; source ~/.bashrc; RAILS_ENV=production bundle exec rake assets:precompile"
appdo があるとき
ssh my.server.example.com sudo -iu web appdo rake assets:precompile
こちらも crontab
と同様、単純になります。
ssh を単純にするのは、 config/deploy.rb
が簡易化できるっていう利点が大きいです。アプリのコードにホスト固有の情報(パスとか環境変数とか)を入れる必要がなくなるので、分離がよくなる。
手順書の簡易化
想像してごらん、すべての cd/source
がドキュメント外に移動する
うっうー
ホスト移行が簡単になる
例えばアプリのパスが異なっていたり、ruby が rvm/rbenv
混在環境であっても、それぞれのホストにある設定ファイルでこの違いを吸収できます。結果、デプロイスクリプトに変更の必要がないので、順次移行がかなり楽になるかもしれません。
その他の活用例
サーバの設定ファイル管理
# /root/.appdo.conf
[apache]
cd = "/etc/apache2"
[bind]
cd = "/var/bind/etc/bind"
cd
先だけ指定しておいて、設定ファイルの管理コマンドだけ共有しておくのも便利かも。
# appdo --app=apache -- ls sites-available
# appdo --app=apache -- ln -s sites-available/99-default sites-enabled
# appdo --app=bind -- co -l master/my.example.com
# appdo --app=bind -- vim master/my.example.com
# appdo --app=bind -- ci -u master/my.example.com
まとめ
- appdo を使って「アプリの文脈」でコマンド実行しよう
- appdo を使って「アプリの文脈」を設定ファイルに落とし込んでしまおう
- appdo を使ってアプリとインフラの切れ目を整理しよう
おわりに呟き
まだ個人で作ったばかりで bullet proof でないスクリプトですが、まずはリリースして仕様叩きをしてみることにしました。バグ報告や要望など、お待ちしています。
もともとは、「rvm/rbenv
を使った際に sudo
から ruby
を使えない」ことがストレスで、この appdo
コマンドを考えました。
昨今インフラに求められているものはまた変化しつつありますが、 docker にせよ serf/consul にせよ autoscaling にせよ、ホストが自律的に動作するように設計したほうがうまく回るように思います。そんなところでも appdo
を使うことにより「ホスト層」のようなものを作っていろいろ吸収できるのであれば、なんか頭使うところが減って幸せになれるんじゃないかなあとかぼんやりと思います。
実装が Python なのはいわゆるファーストプロトタイピング的なやつです。Python でパッケージ作ったことなかったので、実装に際しては @mkouhei さんの bootstrap-py を使用させていただきました。オプション等の要件が固まってきたら Go に移行するのがいいのかなーと考えています。
え?Rustのほうがいいって?…