tag:blogger.com,1999:blog-21849856345552295332023-11-16T00:04:00.698+09:00ゆきろぐゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.comBlogger13125tag:blogger.com,1999:blog-2184985634555229533.post-7680587460080630412015-07-06T15:18:00.001+09:002015-07-06T15:20:40.496+09:00vagrantとapacheと権限<p>vagrantでCakePHPの環境を作ろうと思って色々ハマったのでメモ。<br><br>
ちなみにゲストOSはCentOS7。</p>
<h2>やろうとしたこと</h2>
<ul>
<li>ローカルの(vagrant upしたディレクトリの)app/配下を共有フォルダとしてマウント</li>
<li>appにCakePHPをインストール</li>
<li>共有フォルダとしてマウントした/app/webrootをapacheのドキュメントルートにする</li>
<li>/app配下はapacheユーザで書き込みできるようにする</li>
<li>vagrantのprovisioningでphp、apache、CakePHPのインストールと設定を行う</li>
</ul>
<h2>ハマりポイント1</h2>
<p>最初、apacheで書き込みができるようにということで、synced_folderのオプションでgroupをapacheにし、グループ向けのパーミッションを指定してみた。</p>
<pre><code class="brush:ruby">config.vm.synced_folder "./app", "/app", create: true,
owner: "vagrant", group: "apache",
mount_options: %w(dmode=775 fmode=764)
</code></pre>
<p>apacheがインストールされている場合、この設定自体は問題なく機能する。<br>
が、インストールされていない場合はvagrant upの時点でエラーになる。</p>
<p>どういうことかというと、synced_folderのマウントはOS起動後、provisioningの前に行われるのだが、provisioningが行われていないということはapacheがインストールされておらず、apacheというグループも存在しないためマウントに失敗する。</p>
<ol>
<li>OS起動</li>
<li>synced_folderのマウント ←ここでapacheグループが存在せずエラー</li>
<li>provisioning ←ここまでこない</li>
</ol>
<h3>解決策</h3>
<p>マウント時のユーザとグループは変更せずに、apacheをvagrantグループに追加することにした。</p>
<pre><code class="brush:ruby">config.vm.synced_folder "./app", "/app", create: true,
mount_options: %w(dmode=775 fmode=764)
</code></pre>
<pre><code class="brush:bash">sudo yum -y install httpd
sudo usermod -aG vagrant apache
</code></pre>
<p>apacheがvagrantグループに入るのはどうよ、とも思うけど、開発環境だしまぁいいかなと割り切った。</p>
<p>他にもsynced_folderを設定せずに、apache起動時にゲストOS側で動的にマウントさせるようにするという案もあったが、共有フォルダの設定がVagrantfileから分かれてしまうのが嫌なのと、起動時に小細工が必要なのが面倒だったので採用しなかった。</p>
<h2>ハマりポイント2</h2>
<p>マウントした/appをドキュメントルートにするために、/etc/httpd/conf/httpd.conf内の<code>/var/www/html</code>になっている箇所を全部<code>/app/webroot</code>に置換した。</p>
<p>そしたら<code>/app/webroot must be a directory</code>みたいなエラーが出てapacheが起動しなくなった。<br>
この時点ではまだCakePHPをインストールしていなかったため、/app/webrootが存在していなかった。存在していないディレクトリはDocumentRootに出来ないようだ。</p>
<h3>解決策1</h3>
<p>apacheの設定よりも先にCakePHPをインストールして/app/webrootが存在するようにしておく。<br>
あるいはとりあえず<code>mkdir /app/webroot</code>とかしておく。</p>
<h3>解決策2</h3>
<p>VirtualHostの設定ではDocumentRootが存在していなくても大丈夫らしいのでそちらで設定する。</p>
<pre><code class="brush:text"><VirtualHost *:80>
DocumentRoot /app/webroot
EnableSendfile off
<Directory "/app/webroot">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
</code></pre>
<h2>ハマりポイント3</h2>
<p>app/配下で編集した.cssや画像が更新されない。</p>
<p>これは<code>vagarnt EnableSendfile</code>とかで検索してもらうと事情がわかると思うが、ネットワークマウントしたDocumentRootではカーネルのsendfileを使用したファイル配信はうまくいかないことがあるということらしい。</p>
<h3>解決策</h3>
<p>この機能はオフにする。</p>
<pre><code class="brush:text">EnableSendfile Off
</code></pre>
<p>参考:<a href="http://httpd.apache.org/docs/2.4/ja/mod/core.html#enablesendfile">EnableSendfile ディレクティブ</a></p>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-48639157876777463622014-09-01T13:11:00.001+09:002014-09-01T13:11:47.038+09:00systemd-timesyncdによる時刻同期<p>systemd 213からsystemd-timesyncdというSNTPクライアント機能が追加されて、ntpdやchronyの代わりに使えるようになったんだけど、どうも仮想マシンでは起動しないようになってるようで、VM上のArch Linuxでは使えなかった。</p>
<p><a href="https://github.com/systemd/systemd/commit/01b85ba3add49db47a75a26c80a55d6af24de524">timesyncd: do not start in virtualized environments · 01b85ba · systemd/systemd</a></p>
<h2>使い方</h2>
<p>ntpdやchronyを使用している場合は無効にする。</p>
<pre><code class="brush:bash">sudo systemctl stop ntp.service
sudo systemctl disable ntp.service
</code></pre>
<p>systemd-timesyncdの設定をする。</p>
<pre><code class="brush:bash">sudo -e /etc/systemd/timesyncd.conf
</code></pre>
<pre><code class="brush:plain">[Time]
NTP=ntp.nict.jp
FallbackNTP=time1.google.com time2.google.com time3.google.com time4.google.com
</code></pre>
<p>systemd-timesyncdを有効にして起動する。</p>
<pre><code class="brush:bash">sudo systemctl enable systemd-timesyncd.service
sudo systemctl start systemd-timesyncd.service
</code></pre>
<p>statusでactiveになってればOK<br>
仮想マシンの場合はConditionVirtualization=noに引っかかって起動しない(inactiveになる)。</p>
<pre><code class="brush:bash">sudo systemctl status systemd-timesyncd.service
</code></pre>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-7224728857852638402014-08-21T11:00:00.001+09:002014-08-21T11:00:43.587+09:00FileUtils.chownとFile.chownの違いにハマる<p>えるしっているか、FileUtils.chownのuid/gidは数値も文字列も受け付けるが、File.chownは数値しか受け付けない。</p>
<pre><code class="brush:ruby"># エラー
File.chown('yukithm', 'yukithm', '/path/to/file')
# 数値のuid/gidならOK
File.chown(500, 500, '/path/to/file')
# FileUtilsなら文字列でもOK!
FileUtils.chown('yukithm', 'yukithm', '/path/to/file')
</code></pre>
<p>FileUtilsの実装を覗いてみたら、Etc.getpwnamを使って数値に変換してた。</p>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-76421825484004471242014-05-12T18:38:00.000+09:002014-05-12T18:38:17.485+09:00ESXi上でVirtualBoxがうまく動かなくてハマった件<p>VMware ESXi上の仮想マシンでさらにVirtualBox(Vagrantが使いたかった)を動かしたかったのだけど、すんなり動かなくてハマったのでメモ。</p>
<h2>現象</h2>
<p>vSphere側でハードウェアの仮想化とかVT-xとかの設定は全て有効にした状態でVirtualBoxでゲストOS(64bit)を起動すると、</p>
<pre><code class="brush:plain">IO-APIC(apic pin)1-9,1-11,2-0,2-1,.....2-15
..MP-BIOS bug: 8254 timer not connected IO-APIC
..trying to set up timer as ExtINT..failed.
..tyring to set up timer as BP..IRQ..failed.
kernel panic: IO-APIC + timer dose'nt work!
</code></pre>
<p>のようなエラーがでて起動しない。</p>
<p><a href="http://www.virtualbox.org/manual/ch12.html#ts_linux-buggy">VirtualBoxのマニュアル</a>によると、Linux kernel 2.6.18にレースコンディションがあってうんたらと書いてあって一見それっぽいんだけど、カーネルのバージョンは2.6.32だったので問題はなさそうだった。</p>
<h2>解決方法</h2>
<p>VirtualBoxのゲストOSのIO-APICの無効にすると動いた。</p>
<pre><code class="brush:bash">VBoxManage modifyvm 仮想マシン名 --ioapic off
</code></pre>
<p>VBoxManageコマンドだとこんな感じ。</p>
<pre><code class="brush:plain">config.vm.provider :virtualbox do |vb|
# Don't boot with headless mode
vb.gui = true
vb.customize ["modifyvm", :id, "--ioapic", "off"]
end
</code></pre>
<p>Vagrantfileだとこんな感じ。</p>
<p>あと試してないけどLinux kernelオプションでnoapicを指定するのでも良さそうな気がする。</p>
<p>ただIO-APICはマルチコアCPUでは必須の機能らしいので、コア数を複数割り当てたい場合は諦めるしかないかもしれない。</p>
<p>とりあえずは動かせるようになったけど、なんだかしっくりこないのと新しい仮想マシンを作るたびにいじらなきゃいけないのでテンポ悪いのがなんとも……。</p>
<h2>余談</h2>
<p>仮想マシンをネストさせることをnested virtualizationとかnested vmとか言うらしい。ググる時の参考に。<br>
nested virtualizationは色々難儀なのでできるなら避けたほうが良いと書いている人もいた。</p>
<p>親側の仮想マシンに対して仮想化関係の設定を有効にしてあげないと、そもそも入れ子で動かせなかったり、なんか32bitになったりする。<br>
/proc/cpuinfo見てflagsにvmxがあるかどうかがひとつの判断材料になるとかなんとか。<br>
vSphereでいうとCPUの設定のところの「ハードウェアアシストによる仮想化をゲストOSに公開」とか「CPU/MMX仮想化」のところの「Intel VT-x/AMD-Vを命令セット仮想化に使用し、Intel EPT・AMD RVIをMMU仮想化に使用」あたり。</p>
<p>さくらのVPSとかConoHaとかでVirtualBoxを使ってる記事はわりとよく見るので、親がKVMだとわりとうまくいくのかもしれない。</p>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-3174831971249325622014-05-01T12:25:00.000+09:002014-07-04T15:48:21.846+09:00systemd-networkdでのネットワーク設定<p>systemd v210からsystemdはudevとnetworkdによるネットワーク設定がサポートされるようになった。<br>
これを利用するとNetworkManagerやnetctlといったものを利用せずにsystemd自身でネットワークの設定が行えるようになる(既存のネットワークマネージャの類を使い続けることもできる)。</p>
<p>以下はArch Linuxでの例を挙げるけど、CoreOSやDebianなどでもだいたい同じだと思う。</p>
<h2>networkdの設定ファイル</h2>
<p>systemd-networkdでの設定は<code>/etc/systemd/network</code>ディレクトリに<code>.network</code>、<code>.netdev</code>、<code>.link</code>ファイルを配置していくことになる。</p>
<p>.networkファイル …… マッチするデバイスにネットワークを設定するファイル。<br>
.netdevファイル …… マッチする環境に仮想ネットワークデバイスを作成するファイル。<br>
.linkファイル …… マッチするデバイスのリンクを設定するファイル。</p>
<p>仮想デバイスとかブリッジとか込み入ったことをしない場合は、.networkファイルだけで事足りる。</p>
<h3>resolv.conf</h3>
<p>あと、systemd-resolvedは<code>/etc/resolv.conf</code>を更新してくれない。代わりに<code>/run/systemd/resolve/resolv.conf</code>を生成するので、これに対してリンクを張る必要がある。</p>
<pre><code class="brush:plain"># オリジナルをバックアップ
sudo mv /etc/resolv.conf{,.orig}
# リンクを張る
# systemd 214からファイルパスが変わった模様
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
# systemd 213以前はこちらのパス
sudo ln -s /run/systemd/network/resolv.conf /etc/resolv.conf
</code></pre>
<h2>DHCP</h2>
<p>DHCPを設定する場合は素直にdhcpcdでも起動しておくのが一番手っ取り早いけど、それだと話が終わってしまうのでsystemd-networkdで設定してみる。</p>
<p><code>/etc/systemd/network</code>に<code>dhcp.network</code>みたいなファイルを作成する。ファイル名は任意。</p>
<pre><code class="brush:plain">[Match]
Name=en*
[Network]
DHCP=yes
</code></pre>
<p>こんな感じのファイルを作成して、systemd-networkdを再起動してあげると有効になる。<br>
dhcpcdやnetctlを使用している場合は止めておくこと。</p>
<pre><code class="brush:plain">sudo systemctl restart systemd-networkd.service
</code></pre>
<p>ユニットじゃないのでdaemon-reloadは必要なさそうな気がするけど、もしも設定ファイルを認識しない場合はdaemon-reloadしてみるといいかもしれない。</p>
<pre><code class="brush:plain">sudo systemctl daemon-reload
</code></pre>
<p>セクション名やキーは大文字小文字を区別するっぽいので注意(nameとか書いてハマった)。</p>
<p>ちなみにMatchのNameのところはワイルドカードで指定できるので、インターフェイスが複数ないのであればざっくり指定してもいいかも。<br>
おそらくsystemdとか使ってるケースではインターフェイス名もeth0とかじゃなくてenp2s0みたいなやつだと思うので(この命名規則については参考に挙げているリンクを参照)。<br>
ざっくり言うとOSが認識した順にeth0, eth1とかするんじゃなくて、NICの刺さってるスロットとかの物理的な位置を名前に使おうよ、というお話。<br>
どんな名前かは<code>ip link</code>や<code>ifconfig</code>で確認可能。</p>
<h2>固定IPアドレス</h2>
<p>固定IPアドレスの場合も設定方法はだいたい同じで、.networkに書く内容が違ってくるだけ。</p>
<p>例えば<code>static.network</code>みたいなファイルを作成する。ファイル名は任意。</p>
<pre><code class="brush:plain">[Match]
Name=en*
[Network]
Address=192.168.1.22/24
Gateway=192.168.1.254
DNS=192.168.1.254
</code></pre>
<p>他にどんな設定が書けるのかは<code>man systemd.network</code>(とか<code>man systemd.link</code>)とかを参照。</p>
<h2>ブリッジとか</h2>
<p>ブリッジとかVLANとかはCoreOSの<a href="http://coreos.com/blog/intro-to-systemd-networkd/">Introduction to networkd, network managment from systemd</a>に具体例があるので参考に。</p>
<h2>Docker関係</h2>
<p>Dockerを使うとdocker0みたいなブリッジが必要になるけど、この辺はdockerがbridge-utils(brctl)とかでうまくやってくれるみたいで、特に何も設定しなくても動いた。</p>
<h2>参考</h2>
<p>networkdの設定について</p>
<ul>
<li><a href="https://wiki.archlinux.org/index.php/Systemd-networkd_(%E6%97%A5%E6%9C%AC%E8%AA%9E)">systemd-networkd (日本語) - ArchWiki</a></li>
<li><a href="http://coreos.com/blog/intro-to-systemd-networkd/">Introduction to networkd, network managment from systemd</a></li>
<li><a href="http://coreos.com/docs/cluster-management/setup/network-config-with-networkd/">Network Configuration</a></li>
</ul>
<p>networkdができた背景</p>
<ul>
<li><a href="http://www.reddit.com/r/linux/comments/1rlrph/some_background_on_the_new_systemdnetworkd/">Some background on the new systemd-networkd : linux</a></li>
</ul>
<p>インターフェイス名の命名規則</p>
<ul>
<li><a href="http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/">PredictableNetworkInterfaceNames</a></li>
<li><a href="http://otake.knowd2.com/drupal-rotake/?q=node/165">インターフェイス名eth0をBIOSデバイス名(例:p35p1)に変更しない設定 | ナレッジデザイン大竹のホームページ (by drupal)</a></li>
</ul>ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-79866254916392354522014-04-24T00:07:00.000+09:002014-04-24T00:07:13.209+09:00PackerでVagrantのboxを作成<p>今までVagrantのboxは<a href="https://github.com/jedi4ever/veewee">Veewee</a>を使って作っていたのだけど、今回から<a href="http://www.packer.io/">Packer</a>を使ってみることにした。</p>
<p>PackerはVeeweeみたいにテンプレートが付属していないので自分で用意する必要があるのだけど、一から自分で書くのはハマりどころも多くて大変だなーと思っててずっと手を出していなかったんだよね。<br>
でもまぁみんな野良テンプレートとか作ってるだろうから、それをベースにカスタマイズしてみるかーと検索してたら、Opscode改めChefが<a href="http://opscode.github.io/bento/">Bento</a>っていうPackerのテンプレート集を出しているのを知ったのでこれを使うことにした。</p>
<h2>Packerのインストール</h2>
<p>Packer<br>
<a href="http://www.packer.io/">http://www.packer.io/</a></p>
<p>公式サイトからバイナリパッケージをインストール。</p>
<p>Macの場合はHomebrewからもインストールできる。</p>
<pre><code class="brush:bash">brew tap homebrew/binary
brew install packer
</code></pre>
<h2>Bentoをダウンロード</h2>
<p>Bento by opscode<br>
<a href="http://opscode.github.io/bento/">http://opscode.github.io/bento/</a></p>
<p>公式サイトからアーカイブをダウンロードするかgitでcloneする。<br>
きっとどんどん新しくなっていくだろうからcloneしておくのがいいかな。</p>
<pre><code class="brush:bash">git clone https://github.com/opscode/bento.git
</code></pre>
<h2>boxをビルドしてみる</h2>
<p>bentoのpacker用テンプレートが入っているディレクトリに移動して<code>packer build</code>を実行する。<br>
カスタマイズしたければJSONファイルをいじる。</p>
<pre><code class="brush:bash">cd bento/packer
packer build -only=virtualbox-iso \
-var 'chef_version=latest' \
-var 'mirror=http://ftp.iij.ad.jp/pub/linux/centos' \
centos-6.5-x86_64.json
</code></pre>
<p>いくつかオプションを指定してCentOS 6.5のboxを作成してみた。<br>
そのままだと、テンプレートで定義されてる全builderでboxが生成されてしまうので、<code>-only</code>でVirtualBox用のみ生成するようにした。<br>
<code>-var</code>はテンプレート内の変数を上書き指定するオプションで、ここではISOイメージのミラーサイトを国内にして、最新のChefをインストールするように指定した。<br>
ちなみに、chef_versionを指定しない場合は、omnibus-chefはインストールされないようになっていた。</p>
<p>これでbuildsディレクトリの下にboxが生成されるので、あとは<code>vagrant box add</code>しておしまい。</p>
<pre><code class="brush:bash">vagrant box add CentOS-6.5-x86_64 ../builds/virtualbox/opscode_centos-6.5_chef-latest.box
</code></pre>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-62742454873725363322014-04-16T16:52:00.000+09:002014-04-16T16:52:44.866+09:00Ohai 7でプラグインの仕様が変わった<p>先日リリースされたChef Client 11.12.0から密かに同梱されているOhaiが7.0系に差し替わったのだが、これが結構な仕様変更を含んでいて、Ohai Pluginなどを自作して使ってる人は気をつけないとハマる模様。</p>
<ul>
<li><a href="http://www.getchef.com/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/">Release: Chef Client 11.12.0 & 10.32.2 | Chef Blog</a></li>
<li><a href="https://github.com/opscode/chef/blob/11-stable/RELEASE_NOTES.md#chef-client-breaking-changes">Chef Client 11.12.0 Breaking Changes</a></li>
<li><a href="http://www.getchef.com/blog/2014/01/20/ohai-7-0-release-candidate/">Ohai 7.0 Release Candidate | Chef Blog</a></li>
<li><a href="http://docs.opscode.com/ohai_custom.html">Ohai Custom Plugins — Chef Docs</a></li>
</ul>
<h2>プラグインの仕様変更</h2>
<p>いちおう互換性が保持されるようになっていてv6のプラグインもそのまま動くのだけれど、実行するとこんな感じのWARNINGが出る。</p>
<pre><code class="brush:plain">[2014-04-16T15:50:02+09:00] WARN: [DEPRECATION] Plugin at /tmp/example/plugins/foo.rb is a version 6 plugin. Version 6 plugins will not be supported in future releases of Ohai. Please upgrade your plugin to version 7 plugin syntax. For more information visit here: docs.opscode.com/ohai_custom.html
</code></pre>
<p>細かい点で色々仕様が変わってはいるのだけど、一番大きいのはプラグインの書き方が変わったことと、プラグイン名を指定していた部分がアトリビュート名を指定するように変わったこと。</p>
<h3>新しいプラグインの書き方</h3>
<p>従来のプラグインはこんな感じだった。</p>
<pre><code class="brush:ruby">provides "foo"
require_plugin "baz"
foo Mash.new
foo[:data1] = "foo1"
foo[:data2] = "foo2"
</code></pre>
<p>Ohai 7からはこんな感じになる。</p>
<pre><code class="brush:ruby">Ohai.plugin(:Foo) do
provides "foo"
depends "bar/baz"
collect_data do
foo Mash.new
foo[:data1] = "foo1"
foo[:data2] = "foo2"
end
collect_data(:windows) do
foo Mash.new
foo[:data1] = "win_foo1"
foo[:data2] = "win_foo2"
end
end
</code></pre>
<p>v6ではベタ書きスクリプトっぽかったのが、v7ではオブジェクトっぽく内容を定義する形になった。<br>
上の例には書いていないが、普通にメソッドを書いたり共通処理用のクラスを別途まとめたりもできるようになっているので、複雑な処理が必要な場合にだいぶ書きやすくなったと思う。</p>
<p><code>collect_data</code>がデータを収集する部分なんだけど、引数でプラットフォーム毎の内容に分けて書くことができるようになった。<br>
該当するプラットフォームの<code>collect_data</code>があるとそちらが呼ばれて、なければ<code>:default</code>が指定されている<code>collect_data</code>が呼ばれる(指定しない場合は:default)。</p>
<p>また、v6では<code>require_plugin</code>で依存するプラグインを指定していたが、v7からは<code>depends</code>で依存するアトリビュートを指定するようになった。<br>
利用する側はプラグイン名を知る必要はなく、「どのアトリビュートが使いたい」というのを指定すれば、Ohaiの本体が自動的にそのアトリビュートを提供するプラグインを読み込んでくれる。ここ割りと重要。</p>
<h2>Rubyプログラムから使用する場合の注意点</h2>
<p>プラグインの仕様が変わったことで利用の仕方も少し変わった。</p>
<p>普通に全プラグインを使う場合は従来と同じで<code>Ohai::System#all_plugins</code>を呼び出せば全部実行してくれる。</p>
<pre><code class="brush:ruby">require "ohai"
ohai = Ohai::System.new
ohai.all_plugins
p ohai.data
</code></pre>
<p>自作プラグインなど特定のプラグインだけを実行したい場合は少し変わったのだけど、これがv6用プラグインとv7用プラグインで若干違っていたりしてカオスな感じがする。</p>
<h3>require_pluginを使うパターン</h3>
<p>v6用とv7用が混在している場合はこのやり方しかないように思う。</p>
<pre><code class="brush:ruby">require "ohai"
Ohai::Config[:plugin_path] << "/path/to/oreore/plugins"
ohai = Ohai::System.new
ohai.load_plugins
ohai.require_plugin("foo/bar") # v7の場合はアトリビュート名
ohai.require_plugin("hoge_fuga") # v6の場合はプラグイン名
p ohai.data
</code></pre>
<p>まず<code>load_plugins</code>というのが事前に必要になった。これを呼ばないとプラグインがOhaiに認識されない。<br>
ちなみに<code>load_plugins</code>を呼んでもv6用プラグインは読み込まれない。なぜならv6用プラグインは「読み込まれる=実行される」なので、問答無用で実行されてしまうから。<br>
ただしv6用のプラグインであることは認識される(ソースファイル内の文字列とかで判定してるんだろうか?)。</p>
<p><code>require_plugin</code>にはv7用プラグインの場合はアトリビュート名を、v6用の場合はプラグイン名を指定する。<br>
v7用プラグインは<code>load_plugins</code>によって<code>provides</code>などのメタ情報がロードされるので、それを使って解決しているのだろう。<br>
v6は前述のとおり読み込んでしまったら実行されてしまうので、読み込んでない状態では中に書かれている<code>provides</code>とかも認識されない。よってプラグイン名を指定することになるようだ。<br>
ちなみにv7用プラグインのプラグインを指定しても動かない。</p>
<p>このへんの仕組みは<code>Ohai::ProvidesMap</code>とか<code>Ohai::Loader</code>がやってるようなので、興味のある人はソースを読んでみるといいかも。</p>
<h3>all_pluginsに引数を渡すパターン</h3>
<p>このやり方はv6用プラグインが混在している環境では使えない。<br>
というのも、v6用プラグインは指定できず、無条件に実行されてしまうから(この挙動が仕様なのか現バージョンの不具合なのかは不明)。</p>
<pre><code class="brush:ruby">require "ohai"
Ohai::Config[:plugin_path] << "/path/to/oreore/plugins"
ohai = Ohai::System.new
ohai.all_plugins([
"foo/bar",
"languages/ruby"
])
p ohai.data
</code></pre>
<p><code>all_plugins</code>の引数にアトリビュート名を指定すると、該当するアトリビュートを提供するプラグインのみが実行される。<br>
ただし、(v6用の)プラグイン名は指定できない。そして指定してないにも関わらず、存在するv6プラグインは全て実行される。</p>
<p>ちなみに<code>load_plugins</code>は不要。というか<code>all_plugins</code>が内部で呼び出している。</p>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-13824870240176231722014-04-07T17:09:00.000+09:002014-04-07T17:10:46.921+09:00Rubyでクラスが継承されたら何かする<p>Rubyではクラスが継承されると親クラスの<code>Class#inherited</code>というメソッドが呼ばれる。</p>
<pre><code class="brush:ruby">class Foo
def self.inherited(subclass)
puts "#{self} is inherited by #{subclass}"
end
end
class Bar < Foo
end
Baz = Class.new(Foo)
</code></pre>
<pre><code class="brush:plain">Foo is inherited by Bar
Foo is inherited by #<Class:0x007fdd11969330>
</code></pre>
<p><code>Class.new</code>でサブクラスを生成すると無名のクラスが生成されて、最初に定数に代入されたタイミングでクラスの名前が設定されるというキモい仕様があるので、<code>Class.new(Foo)</code>のタイミングでは名前が出ていない。</p>
<p><code>Class#inherited</code>が呼ばれるタイミングは「クラス定義文の実行直前」とのことなので、上記のBarクラスにあれこれメソッドとか定義してあっても、inheritedが呼ばれた段階ではまだ記述したメソッドが存在しないことに注意。<br>
ちょうど上記のように単に継承しただけの状態と同じクラスになっている。</p>
<p>似たようなものに<code>Module#included</code>や<code>Module#extended</code>がある。それぞれモジュールがinclude/extendされた時に呼ばれる。</p>
<p>こんな機能を使うことはそうそうないと思うけど、仕事で使ってたとあるライブラリではプラグインを自動的にプラグインマネージャ的なものに登録する仕組みとして使われていた。</p>
<h2>参考</h2>
<ul>
<li><a href="http://docs.ruby-lang.org/ja/2.1.0/method/Class/i/inherited.html">instance method Class#inherited</a></li>
<li><a href="http://docs.ruby-lang.org/ja/2.1.0/method/Module/i/included.html">instance method Module#included</a></li>
<li><a href="http://docs.ruby-lang.org/ja/2.1.0/method/Module/i/extended.html">instance method Module#extended</a></li>
<li><a href="http://docs.ruby-lang.org/ja/2.1.0/method/Class/s/new.html">singleton method Class.new</a></li>
</ul>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-32035991332493067882014-03-26T01:34:00.000+09:002014-03-26T15:17:39.210+09:00forkとゾンビプロセス<p>とあるアプリから外部コマンドを実行する機能を実装していたんだけど、ふと気がついたらゾンビプロセスが大量にできていて焦った。</p>
<ul>
<li>とあるアプリはデーモンプロセスでずっと生きている</li>
<li>外部コマンドは終了を待たなくていいし出力も取らなくていい</li>
</ul>
<h2>ゾンビができちゃうダメな実装</h2>
<p>とにかく実行すればいいってことで単純に次のようなコードを書いていた。<br>
forkしてexecするだけという至って単純な実装(実際にはSTDOUTを/dev/nullに向けるとか色々あるけど)。</p>
<pre><code class="brush:ruby"># 外部コマンドを実行する
# コマンドの出力とか終了には興味がない
def run_command(command)
fork do
puts "child: pid=#{Process.pid}, ppid=#{Process.ppid}"
exec(command)
end
end
puts "app: pid=#{Process.pid}"
run_command("/bin/date")
# この後イベントループとかで
# 終了しないデーモンアプリだと思って
sleep 10
</code></pre>
<p>で、これを実行するとこうなる。</p>
<pre><code class="brush:plain">$ ruby zombie.rb
app: pid=4083
child: pid=4114, ppid=4083
2014年 3月 25日 火曜日 23:49:05 JST
</code></pre>
<p>sleepのところで終了しないうちにプロセスを確認するとこんな感じ(関係ない行は省略)。</p>
<pre><code class="brush:plain">$ ps fo ppid,pid,stat,cmd
PPID PID STAT CMD
1491 1493 Ss /bin/zsh
1493 4083 Sl+ \_ ruby zombie.rb
4083 4114 Z+ \_ [date] <defunct>
</code></pre>
<p>実行した外部コマンドであるところのdateが見事にゾンビと化している。</p>
<p>で、zombie.rbが終了するとゾンビプロセスも消滅するのだが、先にも書いたとおり、このアプリは実際にはずーっと起動しているアプリなので終了しない。<br>
ということは、ずーっとゾンビが残ったまま。しかもイベントを受けて定期的に実行する機能なので、どんどんゾンビが増えていく。バイオハザード状態。まずい。</p>
<h2>なぜこんなことに</h2>
<p>通常、子プロセスを生成した後はwaitでその終了を待って終了ステータスを得るのだが、逆に言うとwaitを呼ぶまではたとえ子プロセスが終了していても終了ステータスを保持しておかなければならない。いつ親からwaitを呼ばれるかわからないから。後になってwaitを呼ばれた時に「もうなくなっちゃったから分からないよ」では困るから。これがゾンビプロセスという状態。</p>
<p>つまりさっきの現象はforkするだけしてwaitしていなかったので、終了ステータスの情報だけどんどん残っていってる状態だったわけだ。</p>
<h2>initに任せる</h2>
<p>waitしなかったのが問題なのでwaitすればいいわけだけど、コマンドの実行終了を待ちたくないのでwaitしたくない。<br>
じゃあどうするかというと、問題のプロセスをinitに引き取ってもらう。</p>
<p>initは平たく言うとOSの起動時に最初に実行(PID=1)されてOSの終了時までずっといるやつで、親プロセスがいなくなって孤児になったプロセス(親が先に終了して子だけ残ったプロセス)はinitが引き取ってくれることになっている(initの子プロセスになる)。更にいうと引き取られたプロセスのwaitも面倒見てくれる。</p>
<p>件のプロセスをinitに押し付けるには、forkして自分はすぐに終了するだけというプロセスを一段挟むようにする。</p>
<p>次のような流れになる。</p>
<ol>
<li>親:forkして子を生成する</li>
<li>親:子をwaitする</li>
<li>子:forkして孫を生成する</li>
<li>子:終了する</li>
<li>孫:execする</li>
</ol>
<p>親:アプリケーション<br>
子:initに押し付けるためのプロセス<br>
孫:実行したいコマンド</p>
<p>親は子をforkしたあとにwaitするんだけど、子はforkしたあとにすぐに終了しちゃうので目的のコマンドの終了を待たずともすぐに制御が返ってくる。<br>
孫は(孫の)親であるところの子がwaitせずにとっとと終了してしまうので親がinitに切り替わる。<br>
そして孫の終了はinitが最後まで面倒を見てくれるのでゾンビにはならない。めでたしめでたし。</p>
<h2>修正した実装</h2>
<p>というわけで、上記のように修正したのがこちら(修正したメソッドのみ)。<br>
実行した時に動きがわかりやすいようにsleepとputsが入っている。</p>
<pre><code class="brush:ruby"># 外部コマンドを実行する
# コマンドの出力とか終了には興味がない
def run_command(command)
pid = fork do
puts "child1: pid=#{Process.pid}, ppid=#{Process.ppid}"
fork do
puts "child2: pid=#{Process.pid}, ppid=#{Process.ppid}"
sleep 2
puts "child2: pid=#{Process.pid}, ppid=#{Process.ppid}"
exec(command)
end
sleep 1
end
Process.waitpid(pid)
end
</code></pre>
<p>これを実行するとこうなる。</p>
<pre><code class="brush:plain">app: pid=5064
child1: pid=5095, ppid=5064
child2: pid=5098, ppid=5095 # まだchild1が生きてる時
child2: pid=5098, ppid=1 # child1が終了した後(ppidが1になっている)
2014年 3月 26日 水曜日 01:00:53 JST
</code></pre>
<p>プロセスの変遷はこのような感じ。</p>
<pre><code class="brush:plain"># まだchild1があるとき
$ ps fo ppid,pid,stat,cmd
PPID PID STAT CMD
1491 1493 Ss /bin/zsh
1493 5064 Sl+ \_ ruby no_zombie.rb # 親
5064 5095 Sl+ \_ ruby no_zombie.rb # 子
5095 5098 Sl+ \_ ruby no_zombie.rb # 孫
# child1が終了した後
$ ps fo ppid,pid,stat,cmd
PPID PID STAT CMD
1491 1493 Ss /bin/zsh
1493 5064 Sl+ \_ ruby no_zombie.rb # 親
1 5098 Sl+ ruby no_zombie.rb # 孫(PPIDが1になってる)
# 実行が終了してアプリだけ残ってる状態
# ゾンビプロセスは残ってない!
$ ps fo ppid,pid,stat,cmd
PPID PID STAT CMD
1491 1493 Ss /bin/zsh
1493 5064 Sl+ \_ ruby no_zombie.rb
</code></pre>
<h2>参考</h2>
<ul>
<li><a href="http://hibariya.github.io/entries/20120326/a0.html">プロセスをforkするときのこと - Joy Luck Crab</a></li>
<li><a href="http://d.hatena.ne.jp/sleepy_yoshi/20100228/p1">親プロセスは2度死ぬ - デーモン化に使うダブルforkの謎 - 睡眠不足?!</a></li>
<li><a href="http://hakobe932.hatenablog.com/entry/2013/04/28/210815">なるほどUnixプロセス読んだ - デーモン化のためのdouble fork - はこべブログ ♨</a></li>
<li><a href="http://www.fireproject.jp/feature/c-language/process/fork-wait.html">forkとwaitとゾンビプロセス</a></li>
<li><a href="http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1152001996">Linuxとかのゾンビプロセスって何のためにあるのか教えてください。 fork()した後... - Yahoo!知恵袋</a></li>
<li><a href="http://ja.wikipedia.org/wiki/%E5%AD%90%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9">子プロセス - Wikipedia</a></li>
</ul>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-65819524805994352282014-03-18T18:01:00.000+09:002014-03-18T18:03:11.393+09:00capture3とtimeout<p>Rubyで外部コマンドを実行するには様々な方法があるけど、出力とステータスを取りたい場合は<a href="http://docs.ruby-lang.org/ja/2.1.0/class/Open3.html#M_CAPTURE3">Open3.capture3</a>を使うと簡単に得られる。</p>
<pre><code class="brush:ruby"># リファレンスマニュアルのサンプルから引用
require "open3"
o, e, s = Open3.capture3("echo a; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
p o #=> "a\n"
p e #=> "bar\nbaz\nfoo\n"
p s #=> #<Process::Status: pid 32682 exit 0>
</code></pre>
<p>面倒なパイプの制御などをしなくても簡単に標準入出力が扱える。</p>
<p>ところが、capture3では次のようなタイムアウト処理はうまく動かない。</p>
<pre><code class="brush:ruby">require "timeout"
require "open3"
cmd = "/tmp/heavy.sh"
o, e, s = nil
begin
Timeout.timeout(3) do
o, e, s = Open3.capture3(cmd)
end
rescue Timeout::Error
puts "execution expired"
# pidが取れないのでkillできない
# それどころか、コマンドが終了するまでここに到達しない
end
p o, e, s
</code></pre>
<p>上記のスクリプトを実行すると3秒でTimeout::Errorが発生しそうだけど、実際には発生しない。<br>
何故かと言うと、capture3が内部で使用しているpopen_runという実装の中で、次のようにensureでコマンドの終了を待っているから。</p>
<pre><code class="brush:ruby; first-line:192; highlight:202"> def popen_run(cmd, opts, child_io, parent_io) # :nodoc:
pid = spawn(*cmd, opts)
wait_thr = Process.detach(pid)
child_io.each {|io| io.close }
result = [*parent_io, wait_thr]
if defined? yield
begin
return yield(*result)
ensure
parent_io.each{|io| io.close unless io.closed?}
wait_thr.join
end
end
result
end
</code></pre>
<p>というわけで、タイムアウトしたら中断したい場合は、この辺を紐解いて自前で同じような実装をすることになる。</p>
<p>この場合だとブロックを渡さなければIOと子プロセスのスレッドが返ってくるので、capture3の実装をブロックを使わないで書いたオレオレバージョンとかを作ればいいかもしれない。<br>
ただしそれだとPIDが取れない(Process::StatusはPIDを持っているけど、これは終了するまで手に入らない)ので、popen_runのあたりも含めて自前で実装するはめになる。</p>
<p>なので、前回の<a href="http://yukithm.blogspot.jp/2014/03/kill.html">外部コマンドを実行してタイムアウトしたらkillする</a>ではあえてspawnを直接使って実装したのでした。</p>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-80252329910638220072014-03-14T16:09:00.001+09:002014-03-14T18:17:07.683+09:00外部コマンドを実行してタイムアウトしたらkillする<p>Rubyから外部コマンドを起動して、一定時間が経過しても終了しなかったらkillする、という処理をしようと思って次のようなコードを書いてみた(実際にはSTDOUT, STDERRを取るなどもっと複雑)。</p>
<pre><code class="brush:bash">#!/bin/sh
# Rubyから呼び出される外部コマンド
echo "do something"
sleep 30
echo "done something"</code></pre>
<pre><code class="brush:ruby"># 外部コマンドを実行してタイムアウトしたらkillする
require "timeout"
cmd = "/tmp/heavy.sh"
pid = spawn(cmd)
thr = Process.detach(pid)
begin
Timeout.timeout(3) do
thr.join
end
rescue Timeout::Error
puts "execution expired"
Process.kill(:TERM, pid)
end
status = thr.value
p status</code></pre>
<p>で、早速実行してみると、一見うまく動いているように見えるんだけど、実際にはheavy.shの中で実行しているコマンド(ここではsleep 30)は生き残っている。</p>
<p>実行中</p>
<pre><code class="brush:plain">$ ps xjf
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 1599 1599 1599 ? -1 Ss 500 6:43 tmux
1599 7765 7765 7765 pts/6 9309 Ss 500 0:00 \_ /usr/local/bin/zsh
7765 9309 9309 7765 pts/6 9309 Sl+ 500 0:00 \_ ruby run_command.rb
9309 9311 9309 7765 pts/6 9309 S+ 500 0:00 \_ /bin/sh /tmp/heavy.sh
9311 9315 9309 7765 pts/6 9309 S+ 500 0:00 \_ sleep 30
</code></pre>
<p>killされた後(heavy.shは終了したけどsleep 30は生きてる)</p>
<pre><code class="brush:plain">$ ps xjf
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 9315 9309 7765 pts/6 7765 S 500 0:00 sleep 30
</code></pre>
<p>これはどういうことかというと、UNIX系のOSでは親プロセスが死んで孤児になったプロセスはinitの養子になることが決まっているから(sleep 30のPPIDが1=initになっている)。 <br>
道連れになって親と一緒に死んだりはしない。</p>
<p>で、子だけ生き残って処理が続行されるのは都合が悪いという場合は、次のようなアプローチが考えられる。</p>
<ul>
<li>killしようとしているプロセスの子プロセスを列挙して全部killする</li>
<li>新しいプロセスグループにしてまとめてkillする</li>
</ul>
<p>幸いにもRubyのspawnには:pgroupというプロセスグループを指定するオプションがあるので、これを指定して新しいプロセスグループで実行させることができる。 <br>
そしてProcess.killのpidに負数を指定すると、そのIDのプロセスグループ全体に対してシグナルを投げることができる(ちなみにkillコマンドも同じ仕様)。</p>
<p>通常、プロセスグループIDはグループリーダー、つまりそのグループの最初のプロセスIDになるので、ここではsapwnで返ってきたpidをマイナスにした値(pid=1234なら-1234)を使えばよいことになる。</p>
<p>というわけで、先のRubyスクリプトをこんな感じに修正することで、タイムアウトしたら外部コマンドとそこからさらに実行されたコマンド全てをkillすることができましたとさ。めでたしめでたし。</p>
<pre><code class="brush:ruby">require "timeout"
cmd = "/tmp/heavy.sh"
pid = spawn(cmd, :pgroup => true) # :pgroup => trueを追加
thr = Process.detach(pid)
begin
Timeout.timeout(3) do
thr.join
end
rescue Timeout::Error
puts "execution expired"
Process.kill(:TERM, -pid) # -pidに変更
end
status = thr.value
p status</code></pre>
<p>参考</p>
<ul>
<li><a href="http://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97">プロセスグループ - Wikipedia</a></li>
<li><a href="http://ja.wikipedia.org/wiki/%E5%AD%90%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9">子プロセス - Wikipedia</a></li>
</ul>
ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-15020675959513489902014-03-12T23:37:00.001+09:002014-03-13T00:11:36.715+09:00Rubyの並列処理ライブラリいろいろRubyでワーカースレッドな並列処理がしたくて、なんかいいライブラリないかなーと探してたら見つけたのが次のふたつ(+おまけ)。<br />
<ul>
<li><a href="http://rubygems.org/gems/workers">workers</a></li>
<li><a href="http://rubygems.org/gems/thread">thread</a></li>
</ul>
並列処理だと<a href="http://rubygems.org/gems/parallel">paralell</a>が有名だけど、これは最初に決まった数のタスクがたくさんあって、mapやeachするとその部分がスレッド化してわーっと一斉にやるような感じなので、ちょっと自分が求めているものとは違った。 <br />
今回欲しかったのはそういうのではなくて、ちょうどWebサーバみたいな感じにぽつぽつとタスクがやってきてそれをワーカースレッドに割り振って処理させるようなやつ。 <br />
で、rubygems.orgやruby-toolbox.comを見て良さそうだなと絞ったのが上記のふたつ(これのほうがいいよ!とかあったら教えて下さい)。<br />
<br />
RubyのQueueははじめからスレッド間のFIFOとして設計されているので、これを使えば自分でも簡単にワーカースレッドパターンは作れるのだけど、アプリケーション終了時のワーカーの安全な停止だとか色々な部分に気を配り始めると結構面倒なことになってくるので、それならすでにあるgemを使うほうがいいな、と。<br />
<h2 id="workers">
workers</h2>
workersは何種類かの並列処理パターンを提供していて使い勝手が良さそうに思った。 <br />
目当てのワーカースレッドの他にもparallelと同じようなmap処理や周期的なタイマー処理もあったりして、わりと色々なことができる。<br />
それぞれの分かりやすい使用例が1ページのドキュメントまとまっているので、ここにサンプルコードを書くよりはそっちを見てもらったほうがよいかも。<br />
<br />
Workers <br />
<a href="http://rubydoc.info/gems/workers/0.2.2/frames">http://rubydoc.info/gems/workers/0.2.2/frames</a><br />
<h2 id="thread">
thread</h2>
標準ライブラリともろかぶりな名前のこちらは、<code>require "thread/pool"</code>のようにして組み込みのThreadクラスを拡張する形でスレッドプールやfuture/promise/delayといったいくつかの並列処理パターンを実現してくれる。Everyを使えばworkersのタイマー処理と同じこともできる。<br />
これも各パターンの使用例が1ページにまとまっているので見てもらったほうがよい。<br />
<br />
thread <br />
<a href="http://rubydoc.info/gems/thread/0.1.3/frames">http://rubydoc.info/gems/thread/0.1.3/frames</a><br />
<h2 id="celluloid">
celluloid</h2>
上記の他に<a href="http://rubygems.org/gems/celluloid">celluloid</a>という、いわゆるアクターモデルによる並列処理をするgemもあるんだけど、ちょっと自分には巨大すぎて扱える気がしなかったのでパスした。 <br />
ちょうどcelluloidを利用している別のライブラリで不可解な動きにハマった経験もあったので、容易には手を出すべきではないな、と。<br />
<h2 id="今回はworkersを使ってみた">
今回はworkersを使ってみた</h2>
threadはちょこっと並列処理をするにはシンプルでよいと思うんだけど、今回の用途としてはちょっとシンプル過ぎたのでworkersを採用してみた。 <br />
workersはsynchronizeがあったり、リトライがあったり、成功と失敗をグループ分けできたりと色々な機能がついてるのがちょっと嬉しかった。 <br />
そのうちpromiseとかパイプライン的な処理がしたくなったりしたらthreadを使ってみるのもいいかなーと思っている。ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0tag:blogger.com,1999:blog-2184985634555229533.post-55168693809385514752014-03-12T22:53:00.002+09:002014-03-12T22:57:12.126+09:00技術メモ用のブログ始めてみます技術メモを残すためのブログを始めてみます。<br />
<br />
日々の作業の中で知ったことをどんどん書いていければいいなと思います。<br />
完全で美しい記事ではないかもしれないけれど、手早く新鮮なうちに。ゆーきhttp://www.blogger.com/profile/06406665362457475574noreply@blogger.com0