Help us understand the problem. What is going on with this article?

Itamae/ServerSpecでloginshellを扱う方法 with Capistrano

More than 3 years have passed since last update.

これなに

・Capistrano + Itamae + ServerSpecの構成で、loginshellに書かれた変数をロードする方法について調べたので記録する。

前提

バージョン

serverspec (2.36.0)
itamae (1.9.9)
capistrano (3.5.0)

構成

image

Capistrano経由でリモートサーバー上のItamaeとServerSpecを実行します。
root権限に近いユーザーでCapistranoを実行し、そのまま同じユーザーでItamae/ServerSpecを実行して、各レシピ等にUserを明示する事で、root(みたいな)からusersudoでコマンドを実行させる方式にしています。
理由としては
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を読み取る事ができなかった。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away