これなに
・Capistrano + Itamae + ServerSpecの構成で、loginshellに書かれた変数をロードする方法について調べたので記録する。
前提
バージョン
serverspec (2.36.0)
itamae (1.9.9)
capistrano (3.5.0)
構成
Capistrano経由でリモートサーバー上のItamaeとServerSpecを実行します。
root権限に近いユーザーでCapistranoを実行し、そのまま同じユーザーでItamae/ServerSpecを実行して、各レシピ等にUserを明示する事で、root(みたいな)
からuser
へsudo
でコマンドを実行させる方式にしています。
理由としては
・sudoers
をいじりたくなかった。自動化しずらそうだったから。
・0からセットアップする時はrootユーザーしか存在しないから、それに近い権限から任せてやろうと思った。
何が起きたか
Bundlerがない / セットアップ済のコマンドが見つからない
■ 原因
・Capistranoがリモートホストで実行する時にデフォルトではPATHが引き継がれない。
・リモートホストに ssh
した際に、loginshellが実行されない為、PATHが設定されない。
■ 対策
・Capistranoではリモート先のLoginShellを実行する仕組みは現在存在しない。(仕様)なのであきらめる!
・実行元サーバーとリモートサーバーで設定されるべき変数は同じ(私の場合)だったので、CapistranoOptionを使ってPATHを引き継ぐ事にした。
vi config/deploy.rb
set :default_env, {
PATH: "#{ENV['PATH']}"
}
Itamaeレシピ実行時にコマンドが存在しない / 期待している変数が存在しない
CapistranoがLoginShell使わなくてもItamaeのレシピにUser指定してるし、そこでLoginShell呼べばいいじゃんと思ってたんだけど、
ダメだった話。
CapistranoからPATHを渡してるので、コマンドがない。って事は大概ないんですが、それ以外に期待している変数やdotfileがある場合に動かない。
■ 原因
Itamae実行時はuserが指定された場合に sudo -H -u #{user.shellescape} -- #{shell.shellescape} -c #{command.shellescape}
ってな感じでコマンドを実行している。この部分
sudo
実行時に -i
が渡せるとLoginShellが実行されるが、今のオプション体系では-i
は渡せない。
■ 対策
CapistranoでもItamaeでもLoginShellが実行できない!つまりリモートサーバー側で設定してる変数で作業ができない!
なので、もう、しょうがなく。。。
command
の冒頭で . ~/.bashrc &&
なんて書きました。
※ 私の環境では.bashrcから更に source env.sh
みたいな事してる。
execute "start rabbitmq" do
command ". ~/.bashrc && #{node.ope_script_dir}/rabbitmq-server.sh start"
user "umisora"
only_if "test -e #{node.ope_script_dir}/rabbitmq-server.sh"
end
Immutableとはいえ、login-shellのニーズって結構あると思うんだけどなー。
Recipiの最初の方で bashrc
をセットして、env
はGit Cloneしてきて、あとは良しなに。ってケース。
ServerSpecが意図したユーザーで動いてない
前提として、私の環境はリモートはCapistranoが受け持って、Itamae/ServerSpecはローカル実行です。
> cat spec/spec_helper.rb
require 'serverspec'
set :backend, :exec
さて、こんな状態で以下のコードを実行するとsudo
の動きが見えます。
# 成功 sudoされて -i オプションのおかげでログインShellが呼ばれて動く
describe command("sudo -u umisora -i env | grep -e ^USER 2>&1") do
its(:stdout) { should contain('umisora ') }
end
# 失敗 sudo_optionsは有効ではなく、sudoされてない
describe command("env | grep -e ^USER 2>&1") do
let(:sudo_options) { '-u umisora -i' }
its(:stdout) { should contain('umisora') }
end
■ 原因
・CapistranoはPATH
以外は引き継いでこない。かつ、実行ユーザーはroot
に近い権限のユーザー
・let(:sudo_options)
はServerSpecのadvanced tipsとして紹介されているが、Backendがssh
じゃないと動かない。
・Backendがexec
だと、コマンドはすぐに/bin/sh
に引き渡されてしまう。のでUserを変えるタイミングがない。
■ 対策
sudo -u umisora -i command
でcommandを定義する様にした。
-i
を付与しておく事でumisora
のLoginShellが実行され、変数がもろもろセットされる。
describe command("sudo -u umisora -i env | grep -e ^USER 2>&1") do
end
なんでこんなややこしい事を…
私が構築している環境で、自身で任意の場所にソースのCompile/Installしているケースが多々あり、
デフォルトの/user/bin
とかにPATHを通しているだけでは、依存ライブラリすべてにPATHを通す環境になっていなかった。
さらに、そのユーザーにだけ許可した定義を設定する為に、/home/user/.dotfile
を置いた結果
正しくsudo
ができていないと.dotfile
を読み取る事ができなかった。