パーフェクトRuby写経 特異メソッドの作り方

特異メソッドとは、クラスに定義されたメソッドの他に、そのオブジェクト固有のメソッド持つことができ、それを特異メソッドといいます。

特異メソッドを定義するには、メソッドを定義したいオブジェクトをメソッド名の前に指定してメソッドを記述します。 また、特異メソッドの中でsuperを呼び出すと、クラスに定義されている同名のメソッドが呼び出されます。

class Foo
    def override_me
        p 'in Foo class'
    end

end

foo = Foo.new
bar = Foo.new

def bar.singleton_method
    p 'Called!'
end

def bar.override_me
    p 'in Foo class'
end

bar.singleton_method
bar.override_me

モジュールに定義されたメソッドを、オブジェクトの特異メソッドとして取り込む。 そのさいには、extendを用います。

module Sweet
    def self.lot
        %w(brownie apple-pie bavarois pudding).sample
    end

    def greet_to(name)
        p "Hello, #{name}. My name is #{self.class}"
    end
end

o = Object.new
o.extend Sweet

o.greet_to 'world'
#o.lot

p Sweet.lot

また、self.lotのようにモジュールのメソッド名の前にselfをつけて定義すると、モジュールの特異メソッドとなります。

また、モジュール関数は「privateなインスタンスメソッドであると同時に、モジュールの特異メソッドでもある」メソッドとの事なので、これも特異メソッドの一種のようです。

module MyFunctions
    def my_module_func
        p 'Called!'
    end
    module_function :my_module_func
end

一つのメソッドの場合は上記のように記述し、複数のメソッドを定義さする場合には下記のように記述するようです。

module MyFunctions
    module_function 

    def my_first_func
        p 'first!'
    end

    def my_second_func
        p 'second!'
    end

end

chefdashを試す

chefdash by sidebolt を試してみます。 chefdashはchef-clientのノードの登録や、environment毎やserver毎にcookbookを実行するWebUIを提供するWebアプリケーションです。

必要なものを事前にインストールしておきます。

sudo apt-get install git git-core
sudo apt-get install libssl-dev
sudo ./install.sh
sudo -i -u chefdash

あとはソースコードを取得し、chefdash by sidebolt のRedmeに書いてある手順で実行します。

注意点

chefdashユーザでknifeコマンドを実行できるようにしておく必要があるため、knife configure --initialを実行します。

knife bootstrap用の設定

chefdashでは、それぞれのchef-clientノードにknife bootstrapコマンドを利用してchef-clientのインストール・設定を行うことが可能です。 chefdashで利用しているknife bootstrapのオプションは--sudoです。 特に何も設定しないで、実行すると下記のようなエラーがでます。

f:id:clavier:20131203225559p:plain

このようなエラーが出ますので、事前にrootにパスワードなしでログインできるようになっている必要があります。
chefdashサーバから、chef-clientをインストールするノードにパスワード無しでログインできるようにする必要があります。 chefdashサーバからroot ユーザの環境にSSH の公開鍵と秘密鍵をコピーします。

$ scp .ssh/id_rsa root@chef-clientノードのIPアドレス:/root/.ssh
$ scp .ssh/id_rsa.pub  root@chef-clientノードのIPアドレス:/root/.ssh

chef-clientをインストールされる側のノードでは以下の設定をします。

$ sudo chown root. /root/.ssh/id_rsa
$ sudo chown root. /root/.ssh/id_rsa.pub
$ sudo cat /root/.ssh/id_rsa.pub | sudo tee -a /root/.ssh/authorized_keys

これで、chefdashがknife bootstrapを正常に実行出来るようになります。

f:id:clavier:20131203231715p:plain

chef-clientの実行

chef-clientを実行する前に、ノードでcookbookを実行するようにrun-listを登録しておく必要があります。 chefdashが実際にchef-clientコマンドを実行してCookBookを実行するときは、sshでchef-clientノードに入ってchef-clientコマンドを実行しています。 chefdashが実行しているコマンドは、下記のコマンドです

ssh -o StrictHostKeyChecking=no chef-clientのIPアドレス sudo chef-client

上記のコマンドがパスワードの問い合わせなど発生せずに実行出来る必要があります。 そのためにchefdashユーザがsshでログインできるように各マシンに SSH の公開鍵と秘密鍵をコピーしておく必要があります。

$ scp .ssh/id_rsa root@chef-clientノードのIPアドレス:/home/ubuntu/.ssh
$ scp .ssh/id_rsa.pub root@chef-clientノードのIPアドレス:/home/ubuntu/.ssh
$ sudo chown ubuntu. /home/ubuntu/.ssh/id_rsa
$ sudo chown ubuntu. /home/ubuntu/.ssh/id_rsa.pub
$ sudo cat /home/ubuntu/.ssh/id_rsa.pub | sudo tee -a /home/ubuntu/.ssh/authorized_keys

また、chef-clientノードで、chef-clientコマンドをsudo実行できるように、visudoなどで設定しておく必要があります。 cookbookの実行が成功すると、下記のように表示されます。

f:id:clavier:20131203090545p:plain

雑感

最初はchef-zeroと組み合わせて利用できたら面白いかと思って触ってみが、認証が必要ということであえなく挫折。
chef-serverと組み合わせて確認することになりました。
chef-clientやkinife bootstrapの実行ログ(標準出力に出力される内容)をwebsocketで画面に描画したり、そのあたりの実行結果もwebsocketでやりとりをするという仕組みは面白いと感じました。

複数のサーバに対してcookbookを頻繁に実行するような環境を持っているなら構築する価値はあるかもという感じでした。反面構築時に1回だけ実施するような利用の仕方だと、それほど魅力的はないかと思いました。

f:id:clavier:20131206002329p:plain

VagrantのCentOS 6.4にChef-Serverをインストールしようとはまった

sudo chef-server-ctl reconfigureを実行しようとしたら、次のようなエラーが出た。

Expected process to exit with [0], but received '22'
---- Begin output of curl -sf http://127.0.0.1:8000/_status ----
STDOUT: 
STDERR: 
---- End output of curl -sf http://127.0.0.1:8000/_status ----
Ran curl -sf http://127.0.0.1:8000/_status returned 22


Resource Declaration:
---------------------
# In /opt/chef-server/embedded/cookbooks/chef-server/recipes/bootstrap.rb

 24: execute "verify-system-status" do
 25:   command "curl -sf #{erchef_status_url}"
 26:   retries 20
 27:   not_if { File.exists?(bootstrap_status_file) }
 28: end
 29: 



Compiled Resource:
------------------
# Declared in /opt/chef-server/embedded/cookbooks/chef-server/recipes/bootstrap.rb:24:in `from_file'

execute("verify-system-status") do
  action "run"
  retries 0
  retry_delay 2
  command "curl -sf http://127.0.0.1:8000/_status"
  backup 5
  returns 0
  cookbook_name :"chef-server"
  recipe_name "bootstrap"
  not_if { #code block }
end



[2013-11-29T14:18:42+00:00] ERROR: Running exception handlers
[2013-11-29T14:18:42+00:00] ERROR: Exception handlers complete
Chef Client failed. 9 resources updated
[2013-11-29T14:18:42+00:00] FATAL: Stacktrace dumped to /opt/chef-server/embedded/cookbooks/cache/chef-stacktrace.out
[2013-11-29T14:18:42+00:00] FATAL: Mixlib::ShellOut::ShellCommandFailed: execute[verify-system-status] (chef-server::bootstrap line 24) had an error: Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with [0], but received '22'
---- Begin output of curl -sf http://127.0.0.1:8000/_status ----
STDOUT: 
STDERR: 
---- End output of curl -sf http://127.0.0.1:8000/_status ----
Ran curl -sf http://127.0.0.1:8000/_status returned 22

chef - [chef] Re: Re: Re: Re: Re: Re: Chef v11 fails on Centos 6.4 に全く同じ事象があったのですが、/etc/hostsでホスト名を解決できるようにするとエラーは出なくなります。
また、Vagrantのboxにはある程度(1G以上?)メモリを割り当てないとWebUIにアクセスしたときにnginxが502 Bad Gatewayが出たりするので、reconfigureが正常に終了してもWebUIにアクセスすると502 Bad Gatewayが出るようならメモリ割り当てのサイズを大きくしてみると良いかもしれません。

CentOSにchef-zeroのインストール

chef-zeroとは

chef-zero とは、Chef 11 Server 同等の REST API インターフェースを備えた軽量で簡易な Chef Server です。認証やデータの永続的な保存などを行わない代わりに導入が大変簡単で、Chef Clientなどのテストに有用です。 とのこと。

chef-zeroのインストール

chef-zeroが依存するGemにはRuby拡張をビルドするものがあるため、Development Toolsパッケージグループとruby-develパッケージ、openssl-develパッケージをインストールしておきます。

$ sudo yum groupinstall "Development Tools"
$ sudo yum install ruby-devel openssl-devel

gemが入っていない場合にはインストールします。

$ yum install rubygems
$ ruby -v                  
ruby 1.8.7 (2011-06-30 patchlevel 352) [x86_64-linux]

chef-zeroのインストールを行います。

$ sudo gem install chef-zero json --no-ri --no-rdoc
Building native extensions.  This could take a while...
Successfully installed mixlib-log-1.6.0
Successfully installed hashie-2.0.5
Successfully installed moneta-0.6.0
Successfully installed json-1.8.1
Successfully installed rack-1.5.2
Successfully installed chef-zero-1.7.2
Building native extensions.  This could take a while...
Successfully installed json-1.8.1
7 gems installed

chef-zero を実行します。 とりあえず、全てのサーバから接続を受け付けるようにして起動してみます。

$ chef-zero -H 0.0.0.0 &
[2] 2777
[vagrant@vagrant-centos64 ~]$ >> Starting Chef Zero (v1.7.2)...
>> WEBrick (v1.3.1) on Rack (v1.5) is listening at http://0.0.0.0:8889
>> Press CTRL+C to stop
workstationの設定

今回はworkstationとchef-clientは同一ホストにするので、引き続きchefクライアントのインストールを行います。

$   curl -L https://www.opscode.com/chef/install.sh | bash

つづいて、knifeコマンドを使えるようにしてみます。

$  mkdir /home/vagrant/.chef/
$ vim /home/vagrant/.chef/knife.rb

knife.rbの設定は次のようになります。 client_keyは適当なClientキーを指定し、node_nameも適当に指定します。 client_keyは読み込み可能なprivate key であれば何でも良いです。

log_level               :info
log_location            STDOUT
node_name               'ubuntu1'
client_key              '/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.8.0/spec/data/ssl/private_key.pem'
chef_server_url         'http://10.0.1.9:8889'

動作確認のため利用するレシピはOPSCODEのgetting-startedです。
このレシピはホームディレクトリに、chef-getting-started.txt を作成するレシピです。
レシピをChef Serverにアップロードします。

$ git clone https://github.com/opscode-cookbooks/getting-started.git
$ knife cookbook upload getting-started -o .
管理対象ノード(chef-client)の設定

chef-clientとして実行するには、client.rbが必要なので/etc/chef/client.rbの内容を編集します。 今回は、workstationと同じホストで試しているためnode_nameが同じになっています。 client_keyとvalidation_key は読み込み可能なprivate key であれば何でも良いです。

log_level               :info
log_location            STDOUT
node_name               'ubuntu1'
client_key              '/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.8.0/spec/data/ssl/private_key.pem'
validation_key          '/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.8.0/spec/data/ssl/private_key.pem'
chef_server_url         'http://10.0.1.9:8889'

これでchef-clientを実行すると、chef-zeroサーバにノードが登録されます。

$ sudo chef-client 
[2013-11-25T23:17:00+00:00] INFO: Forking chef instance to converge...
Starting Chef Client, version 11.8.0
[2013-11-25T23:17:00+00:00] INFO: *** Chef 11.8.0 ***
[2013-11-25T23:17:00+00:00] INFO: Chef-client pid: 3811
[2013-11-25T23:17:00+00:00] INFO: Run List is []
[2013-11-25T23:17:00+00:00] INFO: Run List expands to []
[2013-11-25T23:17:00+00:00] INFO: Starting Chef Run for ubuntu1
[2013-11-25T23:17:00+00:00] INFO: Running start handlers
[2013-11-25T23:17:00+00:00] INFO: Start handlers complete.
[2013-11-25T23:17:00+00:00] INFO: HTTP Request Returned 404 Not Found : Object not found: /reports/nodes/ubuntu1/runs
resolving cookbooks for run list: []
[2013-11-25T23:17:00+00:00] INFO: Loading cookbooks []
Synchronizing Cookbooks:
Compiling Cookbooks...
[2013-11-25T23:17:00+00:00] WARN: Node ubuntu1 has an empty run list.
Converging 0 resources
[2013-11-25T23:17:00+00:00] INFO: Chef Run complete in 0.060747315 seconds
[2013-11-25T23:17:00+00:00] INFO: Running report handlers
[2013-11-25T23:17:00+00:00] INFO: Report handlers complete
Chef Client finished, 0 resources updated

$ knife node list
ubuntu1

引き続きノードにrecipeを追加します。

$knife node run_list add ubuntu1 "recipe[getting-started]"

最後にchef-clientを実行します。

$ sudo chef-client                                                                
[2013-11-26T13:18:22+00:00] INFO: Forking chef instance to converge...
Starting Chef Client, version 11.8.0
[2013-11-26T13:18:22+00:00] INFO: *** Chef 11.8.0 ***
[2013-11-26T13:18:22+00:00] INFO: Chef-client pid: 4521
[2013-11-26T13:18:22+00:00] INFO: Run List is [recipe[getting-started]]
[2013-11-26T13:18:22+00:00] INFO: Run List expands to [getting-started]
[2013-11-26T13:18:22+00:00] INFO: Starting Chef Run for ubuntu1
[2013-11-26T13:18:22+00:00] INFO: Running start handlers
[2013-11-26T13:18:22+00:00] INFO: Start handlers complete.
[2013-11-26T13:18:22+00:00] INFO: HTTP Request Returned 404 Not Found : Object not found: /reports/nodes/ubuntu1/runs
resolving cookbooks for run list: ["getting-started"]
[2013-11-26T13:18:22+00:00] INFO: Loading cookbooks [getting-started]
Synchronizing Cookbooks:
[2013-11-26T13:18:22+00:00] INFO: Storing updated cookbooks/getting-started/recipes/default.rb in the cache.
[2013-11-26T13:18:22+00:00] INFO: Storing updated cookbooks/getting-started/attributes/default.rb in the cache.
[2013-11-26T13:18:22+00:00] INFO: Storing updated cookbooks/getting-started/metadata.rb in the cache.
[2013-11-26T13:18:22+00:00] INFO: Storing updated cookbooks/getting-started/README.md in the cache.
  - getting-started
Compiling Cookbooks...
Converging 1 resources
Recipe: getting-started::default
  * template[/home/vagrant/chef-getting-started.txt] action create[2013-11-26T13:18:22+00:00] INFO: Processing template[/home/vagrant/chef-getting-started.txt] action create (getting-started::default line 20)
[2013-11-26T13:18:22+00:00] INFO: template[/home/vagrant/chef-getting-started.txt] created file /home/vagrant/chef-getting-started.txt

    - create new file /home/vagrant/chef-getting-started.txt[2013-11-26T13:18:22+00:00] INFO: template[/home/vagrant/chef-getting-started.txt] updated file contents /home/vagrant/chef-getting-started.txt

    - update content in file /home/vagrant/chef-getting-started.txt from none to e84a13
        --- /home/vagrant/chef-getting-started.txt      2013-11-26 13:18:22.966299400 +0000
        +++ /tmp/chef-rendered-template20131126-4521-17p6nv5    2013-11-26 13:18:22.966299400 +0000
        @@ -1 +1,6 @@
        +Welcome to Chef!
        +
        +This is Chef version 11.8.0.
        +Running on ubuntu.
        +Version 13.04.[2013-11-26T13:18:22+00:00] INFO: template[/home/vagrant/chef-getting-started.txt] mode changed to 644

    - change mode from '' to '0644'

[2013-11-26T13:18:23+00:00] INFO: Chef Run complete in 0.162571175 seconds
[2013-11-26T13:18:23+00:00] INFO: Running report handlers
[2013-11-26T13:18:23+00:00] INFO: Report handlers complete
Chef Client finished, 1 resources updated
雑感

validation_keyのコピーなどが発生しないため比較的簡単に使えるようになりますが、結局server、workstation、clientの構成は必要になるのでchef-soloと比べると手軽さは非常に落ちます。 ただし、Chef 11 Server 同等の REST API インターフェースを備えているということなので、そのあたりに価値を見いだせればお試しとして使ってみるのは良いかもしれません。

ChefのCookBookで、virtualenvを使えるようにする

poise/python · GitHub を使ってPython環境を作ると、自作のCookBookでpipやvirtualenvが使えるようになります。

poise/python · GitHub で は、Pythonのインストール、pipのインストール、virtualenvのインストールを行います。

インストールする場合はbuild-essentialが必要になります。
また、Redhat系の環境にパッケージでインストールするの場合はyumも必要になります。

インストール方法

recipesのdefault.rbを見ると、他のrecipeをincludeしているので、特に何も指定しなくてもpythonのインストール、pipのインストール、virtualenvのインストールが実行されることがわかります。

include_recipe "python::#{node['python']['install_method']}"
include_recipe "python::pip"
include_recipe "python::virtualenv"

パッケージインストールの場合は、run_listにpythonを追加するだけです。 chef-soloだったら、/tmp/vagrant-chef-1/dna.jsonなどに設定。

{
  "run_list" : [
    "recipe[python]"
  ]
}

ソースコードからインストールする場合には、environmentなどに、install_methodとversionを指定します(他にもcheck_sumなども指定できます)。 以下はchef-soloでの設定例です。

{
  "python" : {
    "install_method" : "source",
    "version" : "2.7.5"
  },
  "run_list": [
    "recipe[python]"
  ]
}

ソースコードからインストールする場合には、versionはソースコードを取得する先もある程度指定できます。

remote_file "#{Chef::Config[:file_cache_path]}/Python-#{version}.tar.bz2" do
  source "#{node['python']['url']}/#{version}/Python-#{version}.tar.bz2"
  checksum node['python']['checksum']
  mode "0644"
  not_if { ::File.exists?(install_path) }
end

recipes/source.rb を確認するとわかりますが、拡張子(.tar.bz2)の部分が固定なので、tar.bz2が無い場合にはエラーになってしまうので注意が必要です。
2.7.6は拡張子(.tar.bz2)のものが無いので、現時点ではエラーになりました。

pipやvirtualenvの使い方

pipの使い方は、virtualenvのrecipeを見ればわかるかと思います。

python_pip "virtualenv" do
  action :install
end

virtualenvとpipを使ってFlaskをインストールには、次のようなレシピを作って実行すればOKです。

include_recipe "python::pip"
include_recipe "python::virtualenv"

python_virtualenv "/home/vagrant/apps" do
  action :create
  owner "vagrant"
  group "vagrant"
end

python_pip "Flask" do
  action :install
  virtualenv "/home/vagrant/apps"
end

確認してみると、インストールされていることがわかります。

vagrant@vagrant-ubuntu:~$source ./apps/bin/activate
(apps)vagrant@vagrant-ubuntu:~$ pip list
argparse (1.2.1)
Flask (0.10.1)
itsdangerous (0.23)
Jinja2 (2.7.1)
MarkupSafe (0.18)
pip (1.4.1)
setuptools (0.9.8)
Werkzeug (0.9.4)
wsgiref (0.1.2)