StupidDog's blog

IT関連の手近な所で、疑問に思った事を調べた記録

「Ubuntu13.10 ServerへPuppet(puppet-rbenv)で、rbenvとRubyをインストールする」|ただいまPuppetも修行中です

はじめに

「rbenvを、Ubuntu13.10 ServerへPuppetでインストールする」|ただいまPuppetも修行中です - StupidDog's blog

先日見つけたpuppetでrbenvを扱うモジュールを使ってRubyをインストールします。
知ってしまえば導入も利用簡単ですが、私自身がpuppetを始めたばかりで、いくつか躓いた事がありました。
Rubyのインストールの他に、躓いた事と解決した結果をまとめます。

実行環境

vagrantで、puppetlabsで公開されているboxファイルから構築した仮想端末を使用します。

ubuntu 12.10 desktop(ホストOS)
vagrant 1.4.3
virtualbox 4.3.6 r91406
ubuntu 13.10 server(ゲストOS)
puppet v3.4.2
仮想環境を構築する為に使用したVagrantfileファイルの内容
VAGRANT_API_VERSION = "2"

Vagrant.configure(VAGRANT_API_VERSION) do |config|
  config.vm.box = "ubuntu1310"
  config.vm.box_url = "http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-1310-i386-virtualbox-puppet.box"
  config.vm.hostname = "z003.local"
end

alup-rbenvをインストールする

Puppet forgeのインストール方法に従って、moduleをインストールします。
alup/rbenv/1.2.0 · Puppet Forge

vagrant@z003:/vagrant/manifests$ puppet module install alup-rbenv --version 1.2.0
Notice: Preparing to install into /home/vagrant/.puppet/modules ...
Notice: Downloading from https://forge.puppetlabs.com ...
Notice: Installing -- do not interrupt ...
/home/vagrant/.puppet/modules
└── alup-rbenv (v1.2.0)

マニフェストの作成

マニフェストファイルの保存場所は、ゲストOS/ホストOS間で共有しているディレクトリを使います。
vagrantで構築後にに変更していなければ、ゲストOS上で「/vagrant」ディレクトリになります。
そこへ、「manifests」ディレクトリを作成し、マニフェストファイルを配置します。

Ruby 2.1.1をインストールするmanifestファイルの内容

ファイル名は、dev.ppとして保存します。

$username = 'vagrant'

# rbenvのインストール
rbenv::install { "${username}":
  home => "/home/${username}",
}

# rbenv管理でのRuby 2.1.1のインストール(使用するRubyのバージョンも切換える)
rbenv::compile { "2.1.1":
  user   => "${username}",
  home   => "/home/${username}",
  global => true,
}

マニフェストの適用

vagrant@z003:/vagrant/manifests$ sudo puppet apply -v --modulepath=/home/vagrant/.puppet/modules dev.pp
Notice: Compiled catalog for z003.local in environment production in 0.37 seconds
Info: Applying configuration version '1395567847'
Notice: /Stage[main]/Rbenv::Dependencies::Ubuntu/Package[libxslt1-dev]/ensure: ensure changed 'purged' to 'present'
Notice: /Stage[main]/Rbenv::Dependencies::Ubuntu/Package[git]/ensure: ensure changed 'purged' to 'present'
Notice: /Stage[main]/Rbenv::Dependencies::Ubuntu/Package[libssl-dev]/ensure: ensure changed 'purged' to 'present'
Notice: /Stage[main]/Rbenv::Dependencies::Ubuntu/Package[libyaml-dev]/ensure: ensure changed 'purged' to 'present'
Notice: /Stage[main]/Rbenv::Dependencies::Ubuntu/Package[autoconf]/ensure: ensure changed 'purged' to 'present'
Notice: /Stage[main]/Rbenv::Dependencies::Ubuntu/Package[bison]/ensure: ensure changed 'purged' to 'present'
Notice: /Stage[main]/Rbenv::Dependencies::Ubuntu/Package[libreadline6-dev]/ensure: ensure changed 'purged' to 'present'
Notice: /Stage[main]/Main/Rbenv::Install[vagrant]/Exec[rbenv::checkout vagrant]/returns: executed successfully
Notice: /Stage[main]/Main/Rbenv::Install[vagrant]/File[rbenv::cache-dir vagrant]/ensure: created
Notice: /Stage[main]/Main/Rbenv::Install[vagrant]/File[rbenv::rbenvrc vagrant]/ensure: defined content as '{md5}16cc327d1ce70219d2cd5dfad28ce2e7'
Notice: /Stage[main]/Main/Rbenv::Compile[2.1.1]/Rbenv::Plugin::Rubybuild[rbenv::rubybuild::vagrant]/Rbenv::Plugin[rbenv::plugin::rubybuild::vagrant]/File[rbenv::plugins vagrant]/ensure: created
Notice: /Stage[main]/Main/Rbenv::Compile[2.1.1]/Rbenv::Plugin::Rubybuild[rbenv::rubybuild::vagrant]/Rbenv::Plugin[rbenv::plugin::rubybuild::vagrant]/Exec[rbenv::plugin::checkout vagrant ruby-build]/returns: executed successfully
Notice: /Stage[main]/Main/Rbenv::Compile[2.1.1]/Exec[rbenv::compile vagrant 2.1.1]/returns: executed successfully
Notice: /Stage[main]/Main/Rbenv::Compile[2.1.1]/Rbenv::Gem[rbenv::bundler vagrant 2.1.1]/Rbenvgem[vagrant/2.1.1/bundler/present]/ensure: created
Notice: /Stage[main]/Main/Rbenv::Compile[2.1.1]/Exec[rbenv::rehash vagrant 2.1.1]/returns: executed successfully
Notice: /Stage[main]/Main/Rbenv::Install[vagrant]/Exec[rbenv::shrc vagrant]/returns: executed successfully
Info: Creating state file /var/lib/puppet/state/state.yaml
Notice: Finished catalog run in 804.09 seconds

rbenvでRubyをインストールする部分が少し時間が掛かります。
これで、Rubyだけでなく、git、rbenv、ruby-build(が必要とするpackageも含め)など、関連する必要なファイルがインストールされます。何がインストールされたかは、出力されているメッセージで分かります。

躓いた事

(1) インストールしたはずの「alup-rbenv」モジュールが使用できない

「puppet apply」だけでマニフェストを適用しようとするとエラーとなります。
これは、resource typeの一つであるexecを使用しており、execはroot権限が必要だからです。

vagrant@z003:~$ puppet apply -v /vagrant/manifests/dev.pp 
Notice: Compiled catalog for z003.local in environment production in 0.37 seconds
Error: Parameter user failed on Exec[rbenv::checkout vagrant]: Only root can execute commands as other users at /home/vagrant/.puppet/modules/rbenv/manifests/install.pp:29
Wrapped exception:
Only root can execute commands as other users

エラーメッセージにしたがってroot権限で実行するために、sudoを付けて実行しましたが今度は「rbenv::install」が使用できなくなりました。

vagrant@z003:~$ sudo puppet apply -v /vagrant/manifests/dev.pp
Error: Puppet::Parser::AST::Resource failed with error ArgumentError: Invalid resource type rbenv::install at /vagrant/manifests/dev.pp:7 on node z003.local
Wrapped exception:
Invalid resource type rbenv::install
Error: Puppet::Parser::AST::Resource failed with error ArgumentError: Invalid resource type rbenv::install at /vagrant/manifests/dev.pp:7 on node z003.local

ググってみようにも、まだpuppetに疎すぎて、なかなか検索に使えそうな単語が出てきません。見つけた内容も「includeが足りないんでしょう」ばかりで試してみても変化なしです。
puppetのmoduleを理解すべく「入門puppet」でmoduleについて書かれている章を読み、classだからincludeが必要なのは分かりました。しかし、githubにあるalup-rbenvのコードを見ていたら定義がclassではなくdefineです。

defineについては書かれていないので本家サイトのドキュメントを調べました。
Language: Defined Resource Types — Documentation — Puppet Labs

「defineは、classより簡単にマクロのような処理を書くことができ、それはresource typeのように使用できる」みたいな感じです。
classと違いincludeは不要です。では、何がいけないのでしょう?

defineを調べるために、puppetlabsサイトのドキュメントを読んでいたら、モジュールの配置場所を実行時に与えるオプションの説明がありました。
Module Fundamentals — Documentation — Puppet Labs

そこで、alup-rbenvをインストールした時のログから、インストール先がユーザのホームディレクトリ以下になっていることに気がつきました。

/home/vagrant/.puppet/modules
└── alup-rbenv (v1.2.0)

root権限のための、sudoを付ける前と後でpuppetがモジュールを探しに行く場所が異なってるのではないかと考え、ドキュメンにあった設定値を表示するコマンドで確認したところ、やはり異なっていました。
Module Fundamentals — Documentation — Puppet Labs

vagrant@z003:/vagrant/manifests$ puppet config print modulepath
/home/vagrant/.puppet/modules:/usr/share/puppet/modules

vagrant@z003:/vagrant/manifests$ sudo puppet config print modulepath
/etc/puppet/modules:/usr/share/puppet/modules

これに対応するために、マニフェストの適用時にmodulepathを指定するオプションを付けています。
「sudo puppet apply」のオプションで「--modulepath=/home/vagrant/.puppet/modules」の部分です。

(2) マニフェストの適用は成功しているのにrbenvコマンドが無いと言われる
vagrant@z003:~$ rbenv -v
The program 'rbenv' is currently not installed. You can install it by typing:
sudo apt-get install rbenv

rbenvをgithubからgit cloneでインストールした場合、コマンドのサーチパスに追加が必要になります。
rbenvのドキュメントでは.bashrcに以下の2行を追加するように説明があります。

export PATH="$HOME/bin:$PATH"
eval "$(rbenv init -)"

そして、.bashrcを更新しただけでは、この追加の2行は実行されないので、ターミナルを開き直すか、「source .bashrc」で現在のシェルに反映させる必要があります。alup-rbenvを使ったマニフェストの適用後は、この状態と同じです。
追加のされ方が異なりますが、端末を開き直すか、「source .rbenvrc」でrbenvコマンドが実行できるようになります。
alup-rbenvが上記の2行をどのように追加しているかは、考察にまとめます。

考察

(1) alup-rdenvが、どのようにrbenvのサーチパスを追加しているか

先日、resource typeのexecを使用して、rbenvをgithubから取得する方法を考えていた時に、どう解決するか考えた部分です。
一つの例として、alup-rbenvが行っている解決方法を調べます。
以下は、Ubuntu上での結果なので他の環境とは異なると思いますが、考え方は流用できるはずです。

追加する内容は別ファイルとして作成されます

「.bashrc」ファイルに追加したかった内容は、「.rbenvrc」ファイルとして作成されます。
ファイルの内容は以下の通りで、既にサーチパスに"rbenv"が含まれる場合は追加しません。

#
# This is a shell fragment that initializes rbenv, if it
# has not been inited yet. Managed by puppet - DO NOT EDIT
#
if ! echo $PATH | grep -q rbenv; then
  export PATH="/home/vagrant/.rbenv/bin:$PATH"
  eval "$(rbenv init -)"
fi
「.bashrc」ファイルへの変更は行っていません。

Ubuntuでの話になりますが、「.profile」が実行され、そこから「.bashrc」を呼び出すようにシェルスクリプトが組まれています。
alup-rbenvでは、「.bashrc」には追加せず「.profile」の末尾に以下のコードを追加します。

source /home/vagrant/.rbenvrc

rbenv::installの属性で「.profile」から「.bashrc」へ変更する事もできます。

まとめ

始めに書いたマニフェストから、モジュールを導入して躓いた事で、classとmoduleについて調べ、カスタムresource typeの作成はclass以外に、defineも使える事が分かりました。
そして、alup-rbenvのコードがdefineを使った例として丁度良く、行いたいことが分かっているので理解しやすかったです。

そう、こんな感じに・・・

f:id:StupidDog:20140324235308j:plain
「読める!!読めるぞ!!」