※ Chef + Knife-Zeroな環境を想定してこの記事を書いている。
かなり特殊な状況ではあるが、chef-clientの実行ユーザがubuntuのときにはubuntuユーザの削除を行わないということがしたい。 想定状況としては、Ubuntu VMを新たに立ち上げてbootstrapをかけ、それによりubuntuユーザの削除と別ユーザの追加*1を行うという感じ。 何も考えずに以下のようなレシピを書いても意図した動作はしない。
# knife-zeroの実行時に評価されてしまう user = ENV['SSH_USER'] || ENV['USER'] unless user == 'ubuntu' # Chefの実行ユーザがubuntuでもユーザの削除が行われる user 'ubuntu' do action :remove end end
コードのコメントで書いた通り、knife-zeroの実行時に評価されてしまいchef-clientの実行時に評価されないため意図しない挙動になっている…っと理解しているが間違っているかも。 これを回避するために以下のようにコードを変更した。
ruby_block 'I am ubuntu?' do block do user = ENV['SUDO_USER'] || ENV['USER'] if user != 'ubuntu' resources(user: 'ubuntu').action(:remove) end end end user 'ubuntu' do action :nothing end
ruby_blockリソースはchef-client実行時にブロック内を遅延評価してくれるリソースになっている。 このコードでは、chef-clientの実行ユーザがubuntuではないときのみubuntuリソースをremoveしている。 そして、ubuntuリソースのactionをnothingとして定義しておくことで、removeアクションをキックされない場合(つまりubuntuユーザで実行された場合)は何もせず空振りするようにしている。
node.run_stateとlazy
ruby_blockリソースの公式ページを見ていると、今回みたいな用途では node.run_state
を使うのが良さそうに思える。
しかし、どうもこの node.run_state
はknife-zeroの実行時に評価されるようで最初に書いたコードのように、意図した動作をしなかった。
↓確認したコード
ruby_block 'I am ubuntu?' do block do user = ENV['SUDO_USER'] || ENV['USER'] if user != 'ubuntu' node.run_state['exec_user'] = 'ubuntu' end end end if node.run_state['exec_user'] == 'ubuntu' # node.run_state['exec_user']が常にnilになるので、ubuntuユーザを常に消そうとする。 user 'ubuntu' do action :nothing end end
この問題を回避するためにlazyを使えとある。 しかし、このlazyは直接actionに渡すことができないようで実行時にエラーとなってしまった。
ruby_block 'I am ubuntu?' do block do user = ENV['SUDO_USER'] || ENV['USER'] if user != 'ubuntu' node.run_state['action'] = :remove else node.run_state['action'] = :nothing end end end user 'ubuntu' do action lazy { node.run_state['action'] } end
むずかしい… Chefに詳しくないので間違ったことが書いてあるかも…
別解:あきらめる
初回のbootstrapはコケてしまう前提で、2回boostrapするという運用回避手段も考えられる。まぁそれが手間だったので今回回避策を考えたというのがこの記事の趣旨。 それ以外に、そもそもubuntuユーザを削除せず無効化・ロックなどで対応するという方法も考えられるが、その方法は私の要件には合わなかったという感じ。
cloud-initでVM作成時の処理をあれこれしているので、その中にchefの実行を入れて、その後にubuntuユーザを削除 or そもそもデフォルトユーザを作らないみたいな方法も無きにしもあらずか? 🤔