2015年7月6日月曜日

vagrantとapacheと権限

vagrantでCakePHPの環境を作ろうと思って色々ハマったのでメモ。

ちなみにゲストOSはCentOS7。

やろうとしたこと

  • ローカルの(vagrant upしたディレクトリの)app/配下を共有フォルダとしてマウント
  • appにCakePHPをインストール
  • 共有フォルダとしてマウントした/app/webrootをapacheのドキュメントルートにする
  • /app配下はapacheユーザで書き込みできるようにする
  • vagrantのprovisioningでphp、apache、CakePHPのインストールと設定を行う

ハマりポイント1

最初、apacheで書き込みができるようにということで、synced_folderのオプションでgroupをapacheにし、グループ向けのパーミッションを指定してみた。

config.vm.synced_folder "./app", "/app", create: true,
                owner: "vagrant", group: "apache",
                mount_options: %w(dmode=775 fmode=764)

apacheがインストールされている場合、この設定自体は問題なく機能する。
が、インストールされていない場合はvagrant upの時点でエラーになる。

どういうことかというと、synced_folderのマウントはOS起動後、provisioningの前に行われるのだが、provisioningが行われていないということはapacheがインストールされておらず、apacheというグループも存在しないためマウントに失敗する。

  1. OS起動
  2. synced_folderのマウント ←ここでapacheグループが存在せずエラー
  3. provisioning ←ここまでこない

解決策

マウント時のユーザとグループは変更せずに、apacheをvagrantグループに追加することにした。

config.vm.synced_folder "./app", "/app", create: true,
                mount_options: %w(dmode=775 fmode=764)
sudo yum -y install httpd
sudo usermod -aG vagrant apache

apacheがvagrantグループに入るのはどうよ、とも思うけど、開発環境だしまぁいいかなと割り切った。

他にもsynced_folderを設定せずに、apache起動時にゲストOS側で動的にマウントさせるようにするという案もあったが、共有フォルダの設定がVagrantfileから分かれてしまうのが嫌なのと、起動時に小細工が必要なのが面倒だったので採用しなかった。

ハマりポイント2

マウントした/appをドキュメントルートにするために、/etc/httpd/conf/httpd.conf内の/var/www/htmlになっている箇所を全部/app/webrootに置換した。

そしたら/app/webroot must be a directoryみたいなエラーが出てapacheが起動しなくなった。
この時点ではまだCakePHPをインストールしていなかったため、/app/webrootが存在していなかった。存在していないディレクトリはDocumentRootに出来ないようだ。

解決策1

apacheの設定よりも先にCakePHPをインストールして/app/webrootが存在するようにしておく。
あるいはとりあえずmkdir /app/webrootとかしておく。

解決策2

VirtualHostの設定ではDocumentRootが存在していなくても大丈夫らしいのでそちらで設定する。

<VirtualHost *:80>
    DocumentRoot /app/webroot
    EnableSendfile off

    <Directory "/app/webroot">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

ハマりポイント3

app/配下で編集した.cssや画像が更新されない。

これはvagarnt EnableSendfileとかで検索してもらうと事情がわかると思うが、ネットワークマウントしたDocumentRootではカーネルのsendfileを使用したファイル配信はうまくいかないことがあるということらしい。

解決策

この機能はオフにする。

EnableSendfile Off

参考:EnableSendfile ディレクティブ

2014年9月1日月曜日

systemd-timesyncdによる時刻同期

systemd 213からsystemd-timesyncdというSNTPクライアント機能が追加されて、ntpdやchronyの代わりに使えるようになったんだけど、どうも仮想マシンでは起動しないようになってるようで、VM上のArch Linuxでは使えなかった。

timesyncd: do not start in virtualized environments · 01b85ba · systemd/systemd

使い方

ntpdやchronyを使用している場合は無効にする。

sudo systemctl stop ntp.service
sudo systemctl disable ntp.service

systemd-timesyncdの設定をする。

sudo -e /etc/systemd/timesyncd.conf
[Time]
NTP=ntp.nict.jp
FallbackNTP=time1.google.com time2.google.com time3.google.com time4.google.com

systemd-timesyncdを有効にして起動する。

sudo systemctl enable systemd-timesyncd.service
sudo systemctl start systemd-timesyncd.service

statusでactiveになってればOK
仮想マシンの場合はConditionVirtualization=noに引っかかって起動しない(inactiveになる)。

sudo systemctl status systemd-timesyncd.service

2014年8月21日木曜日

FileUtils.chownとFile.chownの違いにハマる

えるしっているか、FileUtils.chownのuid/gidは数値も文字列も受け付けるが、File.chownは数値しか受け付けない。

# エラー
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')

FileUtilsの実装を覗いてみたら、Etc.getpwnamを使って数値に変換してた。

2014年5月12日月曜日

ESXi上でVirtualBoxがうまく動かなくてハマった件

VMware ESXi上の仮想マシンでさらにVirtualBox(Vagrantが使いたかった)を動かしたかったのだけど、すんなり動かなくてハマったのでメモ。

現象

vSphere側でハードウェアの仮想化とかVT-xとかの設定は全て有効にした状態でVirtualBoxでゲストOS(64bit)を起動すると、

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!

のようなエラーがでて起動しない。

VirtualBoxのマニュアルによると、Linux kernel 2.6.18にレースコンディションがあってうんたらと書いてあって一見それっぽいんだけど、カーネルのバージョンは2.6.32だったので問題はなさそうだった。

解決方法

VirtualBoxのゲストOSのIO-APICの無効にすると動いた。

VBoxManage modifyvm 仮想マシン名 --ioapic off

VBoxManageコマンドだとこんな感じ。

config.vm.provider :virtualbox do |vb|
  # Don't boot with headless mode
  vb.gui = true

  vb.customize ["modifyvm", :id, "--ioapic", "off"]
end

Vagrantfileだとこんな感じ。

あと試してないけどLinux kernelオプションでnoapicを指定するのでも良さそうな気がする。

ただIO-APICはマルチコアCPUでは必須の機能らしいので、コア数を複数割り当てたい場合は諦めるしかないかもしれない。

とりあえずは動かせるようになったけど、なんだかしっくりこないのと新しい仮想マシンを作るたびにいじらなきゃいけないのでテンポ悪いのがなんとも……。

余談

仮想マシンをネストさせることをnested virtualizationとかnested vmとか言うらしい。ググる時の参考に。
nested virtualizationは色々難儀なのでできるなら避けたほうが良いと書いている人もいた。

親側の仮想マシンに対して仮想化関係の設定を有効にしてあげないと、そもそも入れ子で動かせなかったり、なんか32bitになったりする。
/proc/cpuinfo見てflagsにvmxがあるかどうかがひとつの判断材料になるとかなんとか。
vSphereでいうとCPUの設定のところの「ハードウェアアシストによる仮想化をゲストOSに公開」とか「CPU/MMX仮想化」のところの「Intel VT-x/AMD-Vを命令セット仮想化に使用し、Intel EPT・AMD RVIをMMU仮想化に使用」あたり。

さくらのVPSとかConoHaとかでVirtualBoxを使ってる記事はわりとよく見るので、親がKVMだとわりとうまくいくのかもしれない。

2014年5月1日木曜日

systemd-networkdでのネットワーク設定

systemd v210からsystemdはudevとnetworkdによるネットワーク設定がサポートされるようになった。
これを利用するとNetworkManagerやnetctlといったものを利用せずにsystemd自身でネットワークの設定が行えるようになる(既存のネットワークマネージャの類を使い続けることもできる)。

以下はArch Linuxでの例を挙げるけど、CoreOSやDebianなどでもだいたい同じだと思う。

networkdの設定ファイル

systemd-networkdでの設定は/etc/systemd/networkディレクトリに.network.netdev.linkファイルを配置していくことになる。

.networkファイル …… マッチするデバイスにネットワークを設定するファイル。
.netdevファイル …… マッチする環境に仮想ネットワークデバイスを作成するファイル。
.linkファイル …… マッチするデバイスのリンクを設定するファイル。

仮想デバイスとかブリッジとか込み入ったことをしない場合は、.networkファイルだけで事足りる。

resolv.conf

あと、systemd-resolvedは/etc/resolv.confを更新してくれない。代わりに/run/systemd/resolve/resolv.confを生成するので、これに対してリンクを張る必要がある。

# オリジナルをバックアップ
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

DHCP

DHCPを設定する場合は素直にdhcpcdでも起動しておくのが一番手っ取り早いけど、それだと話が終わってしまうのでsystemd-networkdで設定してみる。

/etc/systemd/networkdhcp.networkみたいなファイルを作成する。ファイル名は任意。

[Match]
Name=en*

[Network]
DHCP=yes

こんな感じのファイルを作成して、systemd-networkdを再起動してあげると有効になる。
dhcpcdやnetctlを使用している場合は止めておくこと。

sudo systemctl restart systemd-networkd.service

ユニットじゃないのでdaemon-reloadは必要なさそうな気がするけど、もしも設定ファイルを認識しない場合はdaemon-reloadしてみるといいかもしれない。

sudo systemctl daemon-reload

セクション名やキーは大文字小文字を区別するっぽいので注意(nameとか書いてハマった)。

ちなみにMatchのNameのところはワイルドカードで指定できるので、インターフェイスが複数ないのであればざっくり指定してもいいかも。
おそらくsystemdとか使ってるケースではインターフェイス名もeth0とかじゃなくてenp2s0みたいなやつだと思うので(この命名規則については参考に挙げているリンクを参照)。
ざっくり言うとOSが認識した順にeth0, eth1とかするんじゃなくて、NICの刺さってるスロットとかの物理的な位置を名前に使おうよ、というお話。
どんな名前かはip linkifconfigで確認可能。

固定IPアドレス

固定IPアドレスの場合も設定方法はだいたい同じで、.networkに書く内容が違ってくるだけ。

例えばstatic.networkみたいなファイルを作成する。ファイル名は任意。

[Match]
Name=en*

[Network]
Address=192.168.1.22/24
Gateway=192.168.1.254
DNS=192.168.1.254

他にどんな設定が書けるのかはman systemd.network(とかman systemd.link)とかを参照。

ブリッジとか

ブリッジとかVLANとかはCoreOSのIntroduction to networkd, network managment from systemdに具体例があるので参考に。

Docker関係

Dockerを使うとdocker0みたいなブリッジが必要になるけど、この辺はdockerがbridge-utils(brctl)とかでうまくやってくれるみたいで、特に何も設定しなくても動いた。

参考

networkdの設定について

networkdができた背景

インターフェイス名の命名規則

2014年4月24日木曜日

PackerでVagrantのboxを作成

今までVagrantのboxはVeeweeを使って作っていたのだけど、今回からPackerを使ってみることにした。

PackerはVeeweeみたいにテンプレートが付属していないので自分で用意する必要があるのだけど、一から自分で書くのはハマりどころも多くて大変だなーと思っててずっと手を出していなかったんだよね。
でもまぁみんな野良テンプレートとか作ってるだろうから、それをベースにカスタマイズしてみるかーと検索してたら、Opscode改めChefがBentoっていうPackerのテンプレート集を出しているのを知ったのでこれを使うことにした。

Packerのインストール

Packer
http://www.packer.io/

公式サイトからバイナリパッケージをインストール。

Macの場合はHomebrewからもインストールできる。

brew tap homebrew/binary
brew install packer

Bentoをダウンロード

Bento by opscode
http://opscode.github.io/bento/

公式サイトからアーカイブをダウンロードするかgitでcloneする。
きっとどんどん新しくなっていくだろうからcloneしておくのがいいかな。

git clone https://github.com/opscode/bento.git

boxをビルドしてみる

bentoのpacker用テンプレートが入っているディレクトリに移動してpacker buildを実行する。
カスタマイズしたければJSONファイルをいじる。

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

いくつかオプションを指定してCentOS 6.5のboxを作成してみた。
そのままだと、テンプレートで定義されてる全builderでboxが生成されてしまうので、-onlyでVirtualBox用のみ生成するようにした。
-varはテンプレート内の変数を上書き指定するオプションで、ここではISOイメージのミラーサイトを国内にして、最新のChefをインストールするように指定した。
ちなみに、chef_versionを指定しない場合は、omnibus-chefはインストールされないようになっていた。

これでbuildsディレクトリの下にboxが生成されるので、あとはvagrant box addしておしまい。

vagrant box add CentOS-6.5-x86_64 ../builds/virtualbox/opscode_centos-6.5_chef-latest.box

2014年4月16日水曜日

Ohai 7でプラグインの仕様が変わった

先日リリースされたChef Client 11.12.0から密かに同梱されているOhaiが7.0系に差し替わったのだが、これが結構な仕様変更を含んでいて、Ohai Pluginなどを自作して使ってる人は気をつけないとハマる模様。

プラグインの仕様変更

いちおう互換性が保持されるようになっていてv6のプラグインもそのまま動くのだけれど、実行するとこんな感じのWARNINGが出る。

[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

細かい点で色々仕様が変わってはいるのだけど、一番大きいのはプラグインの書き方が変わったことと、プラグイン名を指定していた部分がアトリビュート名を指定するように変わったこと。

新しいプラグインの書き方

従来のプラグインはこんな感じだった。

provides "foo"

require_plugin "baz"

foo Mash.new
foo[:data1] = "foo1"
foo[:data2] = "foo2"

Ohai 7からはこんな感じになる。

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

v6ではベタ書きスクリプトっぽかったのが、v7ではオブジェクトっぽく内容を定義する形になった。
上の例には書いていないが、普通にメソッドを書いたり共通処理用のクラスを別途まとめたりもできるようになっているので、複雑な処理が必要な場合にだいぶ書きやすくなったと思う。

collect_dataがデータを収集する部分なんだけど、引数でプラットフォーム毎の内容に分けて書くことができるようになった。
該当するプラットフォームのcollect_dataがあるとそちらが呼ばれて、なければ:defaultが指定されているcollect_dataが呼ばれる(指定しない場合は:default)。

また、v6ではrequire_pluginで依存するプラグインを指定していたが、v7からはdependsで依存するアトリビュートを指定するようになった。
利用する側はプラグイン名を知る必要はなく、「どのアトリビュートが使いたい」というのを指定すれば、Ohaiの本体が自動的にそのアトリビュートを提供するプラグインを読み込んでくれる。ここ割りと重要。

Rubyプログラムから使用する場合の注意点

プラグインの仕様が変わったことで利用の仕方も少し変わった。

普通に全プラグインを使う場合は従来と同じでOhai::System#all_pluginsを呼び出せば全部実行してくれる。

require "ohai"

ohai = Ohai::System.new
ohai.all_plugins

p ohai.data

自作プラグインなど特定のプラグインだけを実行したい場合は少し変わったのだけど、これがv6用プラグインとv7用プラグインで若干違っていたりしてカオスな感じがする。

require_pluginを使うパターン

v6用とv7用が混在している場合はこのやり方しかないように思う。

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

まずload_pluginsというのが事前に必要になった。これを呼ばないとプラグインがOhaiに認識されない。
ちなみにload_pluginsを呼んでもv6用プラグインは読み込まれない。なぜならv6用プラグインは「読み込まれる=実行される」なので、問答無用で実行されてしまうから。
ただしv6用のプラグインであることは認識される(ソースファイル内の文字列とかで判定してるんだろうか?)。

require_pluginにはv7用プラグインの場合はアトリビュート名を、v6用の場合はプラグイン名を指定する。
v7用プラグインはload_pluginsによってprovidesなどのメタ情報がロードされるので、それを使って解決しているのだろう。
v6は前述のとおり読み込んでしまったら実行されてしまうので、読み込んでない状態では中に書かれているprovidesとかも認識されない。よってプラグイン名を指定することになるようだ。
ちなみにv7用プラグインのプラグインを指定しても動かない。

このへんの仕組みはOhai::ProvidesMapとかOhai::Loaderがやってるようなので、興味のある人はソースを読んでみるといいかも。

all_pluginsに引数を渡すパターン

このやり方はv6用プラグインが混在している環境では使えない。
というのも、v6用プラグインは指定できず、無条件に実行されてしまうから(この挙動が仕様なのか現バージョンの不具合なのかは不明)。

require "ohai"

Ohai::Config[:plugin_path] << "/path/to/oreore/plugins"

ohai = Ohai::System.new
ohai.all_plugins([
  "foo/bar",
  "languages/ruby"
])

p ohai.data

all_pluginsの引数にアトリビュート名を指定すると、該当するアトリビュートを提供するプラグインのみが実行される。
ただし、(v6用の)プラグイン名は指定できない。そして指定してないにも関わらず、存在するv6プラグインは全て実行される。

ちなみにload_pluginsは不要。というかall_pluginsが内部で呼び出している。