StupidDog's blog

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

Macのbashビルトインコマンドのtimeと/usr/bin/timeは別物

はじめに

あるコマンドの時間計測やリソース使用量を表示する time と言うコマンドがあります。
このコマンドを使い実行時のメモリー使用量を測ろうかと思ったところで問題が発生しました。

問題

Mactimeコマンドを使おうとすると bashビルトインのコマンドが呼び出されてしまいリソース使用量は取得できないので、別途インストールが必要なのだと思って GNU timeHomebrewでインストールしたが以下のバグが残ったままでした。
GNU Time にある壮大なバグ - Qiita

対応

そこで調べてみると bashビルトイン以外に timeコマンドがあることが分かりました。
/usr/bin/timeならばリソース使用量も表示できます。

$ type -a time
time is a shell keyword
time is /usr/bin/time

bashビルトインコマンドのヘルプはhelp で確認できます。

$ help time
time: time [-p] PIPELINE
    Execute PIPELINE and print a summary of the real time, user CPU time,
    and system CPU time spent executing PIPELINE when it terminates.
    The return status is the return status of PIPELINE.  The `-p' option
    prints the timing summary in a slightly different format.  This uses
    the value of the TIMEFORMAT variable as the output format.
times: times
    Print the accumulated user and system times for processes run from
    the shell.

ビルトインではない timeコマンドのヘルプはmanコマンドで確認できます。
BSD系の timeコマンドがベースなのは分かります。
(分岐したOS毎に出力やオプションが変化しているので確認が必要)

TIME(1)                   BSD General Commands Manual                  TIME(1)

NAME
     time -- time command execution

SYNOPSIS
     time [-lp] utility

DESCRIPTION
  ...

確認

GNU timeでバグの確認方法で使用されていたコマンドにより、目的のメモリー使用量を測れることが確認できました。
maximum resident set sizeの値がバイト単位で出力されている。

$ /usr/bin/time -l perl -e '"X" x 400 x 1024 x 1024'
        0.27 real         0.12 user         0.14 sys
 421330944  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
    103186  page reclaims
         0  page faults
         0  swaps
         0  block input operations
         0  block output operations
         0  messages sent
         0  messages received
         0  signals received
         0  voluntary context switches
       315  involuntary context switches

おまけ

GNU timeの壮大なバグについては、各配布先環境でパッチが当てられています。

このmaximum resident set sizeが曲者で、この値の元はシステムコールgetrusage(2)ru_maxrssの値から算出しています。
しかし、OS毎にru_maxrssの単位が変わります。

manコマンドで確認したところ

OS 単位
OS X 10.11.3 バイト
FreeBSD 11.0 キロバイト
Linux 2.6.32~ キロバイト

そして元のコードでは、ページ単位として処理されている。
「(ru_maxrss * pagesize) / 1024」の計算を行いキロバイト単位の結果を得ている。
pagesizeが 4096(4K) なので4倍なる。

そしてHomebrewでインストールするGNU timeは、全くパッチの当たっていないGNU time ver 1.7FreeBSDftpサーバーから取得しているために壮大なバグが残ったままです。

…してHomebrewgnu-timeフォーミュラにパッチを追加してプルリクエストしちゃったのですが、upstreamにイシュってと…

  • OS毎(getrusage)に単位が違う値を返してくるgetrusage
  • 元のコードは作成時のru_maxrssがページ単位だったのでコードは正しい
  • 現在、各配布先で独自のパッチがある

こんな感じでバグとは言えないけど、OS毎の対応を行うなら新旧含めて、どこまで対応するのか難しい。
んで、結論は各環境でパッチによる対応が良いのでは...Red Hatとか別のパッチもあたってるし...
そして、これらを英語で説明できるほど達者じゃ無い...ノω≦)これが一番高いハードル。

「PHPのforeach(range())は、forループの代用にはならないよ」

はじめに

添字などのループ処理に「for」ではなく「foreachとrange」を組み合わせたループで添字をカウントアップしているコードを見かけました。
見た目は、他の言語のイテレータのような雰囲気を出しているが、これはPHPでダメな書き方なのでメモ。

説明

何がダメかと言うと、range()はイテレータ(or ジェネレータ)を生成するのでなく、配列を生成する。
10回くらいのループならば気が付かないかもしれません。

<?php
foreach(range(1, 10) as $i) {
  echo $i . PHP_EOL;
}

でも、100万回のループならば・・・

<?php
foreach(range(1, 1000000) as $i) {
  echo $i . PHP_EOL;
}

メモリーを確保出来ずにエラーとなります。
php.iniのmemory_limitをデフォルト値の128Mから大きくしても、根本の問題は解決しません。

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32 bytes) in xxx.php

まとめ

シンタックスが似ているだけで、RubyPerlのrange表現とは別物です。
この処理の問題は、処理速度ではなくRangeで指定した分の配列をメモリに確保することです!ただの添字をループさせるだけなら素直にforをつかいましょう。
古いブログで処理速度だけの検証で優劣をつけて紹介されているため、意外と多く見かけます。
稼働後に負荷かが大きくなった時に障害になるのでコードレビューで気を付けたいところです。

「Windows7でVagrantを更新インストールした環境で、"bsdtarが見つからない"となる件について」

はじめに

Windows7上で、インストーラによりVagrantを更新インストールした環境で発生した問題について原因と対応をまとめました。
(2014.10.13時点)

環境

Windows7 Professional (64bit)
Vagrant 1.5.2 (更新前)
Vagrant 1.6.5 (更新後)

問題

問題を発生させる手順
  1. Vagrnat 1.5.2 がインストールされている状態で、1.6.5 用のインストーラからインストールする。
  2. Boxファイルの追加(vagrant box add)を行う。
発生した問題

下記のエラーメッセージが表示されBoxファイルの追加に失敗する。

F:\vm\z001>type vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu1404"
  config.vm.box_url = "f:\\boxs\\ubuntu-14-04-x64-virtualbox.box"
end

F:\vm\z001>vagrant box add ubuntu1404 "f:\\boxs\\ubuntu-14-04-x64-virtualbox.box"
==> box: Adding box 'ubuntu1404' (v0) for provider:
    box: Downloading: file://f:/boxs/ubuntu-14-04-x64-virtualbox.box
    box: Progress: 100% (Rate: 163M/s, Estimated time remaining: --:--:--)
The executable 'bsdtar' Vagrant is trying to run was not
found in the %PATH% variable. This is an error. Please verify
this software is installed and on the path.

エラーメッセージは「実行ファイルの"bsdtar"がサーチパス上から見つからない。インストールしてパスを通してください」みたいな感じです。

対応

Vagrant 1.5.2 をアンインストールしてから、Vagrant 1.6.5 を新規インストールする。
ネット上を調べると、「bsdtar.exeを別途インストールする」や「PATH環境変数へ、別の場所にあるbsdtar.exeのパスを追加する」などが見つかりましたが、新規インストールが現状での最良だと思います。(理由は後ほど)

調査と原因

まず、vagrant 1.6.5を新規インストールした場合と、更新インストールした場合で何が異なるのか?
bsdtar.exeに注目して調べると「C:\HashiCorp\Vagrant\embedded\gnuwin32\bin」の内容が異なります。

更新インストールした場合
C:\HashiCorp\Vagrant\embedded\gnuwin32\bin>dir
 ドライブ C のボリューム ラベルは SYSTEM(SSD) です
 ボリューム シリアル番号は 8A9F-F559 です

 C:\HashiCorp\Vagrant\embedded\gnuwin32\bin のディレクトリ

2014/10/13  18:53    <DIR>          .
2014/10/13  18:53    <DIR>          ..
2014/04/29  22:48         1,209,161 libarchive.dll
               1 個のファイル           1,209,161 バイト
               2 個のディレクトリ  176,688,869,376 バイトの空き領域
新規インストールした場合
C:\HashiCorp\Vagrant\embedded\gnuwin32\bin>dir
 ドライブ C のボリューム ラベルは SYSTEM(SSD) です
 ボリューム シリアル番号は 8A9F-F559 です

 C:\HashiCorp\Vagrant\embedded\gnuwin32\bin のディレクトリ

2014/10/13  19:28    <DIR>          .
2014/10/13  19:28    <DIR>          ..
2014/04/29  22:50         1,252,396 bsdcpio.exe
2014/04/29  22:50         1,281,328 bsdtar.exe
2014/04/29  22:48         1,209,161 libarchive.dll
               3 個のファイル           3,742,885 バイト
               2 個のディレクトリ  176,117,678,080 バイトの空き領域

「bsdtar.exe」が無くなっています。
これはインストーラにより削除されたことになるのですが、更新インストール時のログに原因となりそうなメッセージが出力されています。

更新インストール時のログ(抜粋)
...
MSI (c) (AC:30) [18:49:28:836]: Disallowing installation of component: {BCBBCB44-984B-4F12-9EAE-9F32C977168F} since the same component with higher versioned keyfile exists
MSI (c) (AC:30) [18:49:28:836]: Disallowing installation of component: {46C1C17F-C76E-4445-ACC8-D4FEE653035F} since the same component with higher versioned keyfile exists
...
MSI (s) (88:64) [18:50:35:698]: Executing op: ComponentRegister(ComponentId={BCBBCB44-984B-4F12-9EAE-9F32C977168F},KeyPath=C:\HashiCorp\Vagrant\embedded\gnuwin32\bin\bsdtar.exe,State=3,,Disk=1,SharedDllRefCount=2,BinaryType=0)
1: {3D24EE12-E0CF-41EC-8182-361ECF575656} 2: {BCBBCB44-984B-4F12-9EAE-9F32C977168F} 3: C:\HashiCorp\Vagrant\embedded\gnuwin32\bin\bsdtar.exe 
...

「Disallowing installation of component: {GUID} since the same component with higher versioned keyfile exists」が、「既に更新が必要のないバージョンが存在しているのでインストールしません」な感じです。
長い英数字文字列{GUID}が対象ファイルを表しています。ログの後半で{GUID}に対応するファイルが「gunwin32\bin\bsdtar.exe」であることが確認できます。

Windows Installer の 更新時のルール(
File Versioning Rules (Windows))で判定した結果、インストール対象から外されているようです。(Vagrant 1.5.2 と 1.6.5 でインストールされる「bsdtar.exe」は同じなので更新する必要がないのですが)

その後、何故かインストール処理中に「gunwin32\bin」以下のファイルが削除されます。
「bsdtar.exe」も一緒に削除された後、インストール対象から外れていためインストールされず「bsdtar.exe」が消えてしまう結果となるようです。

まとめ

msi形式のインストーラの作成時の設定か、Windows Installer特有の問題なのかは別として、vagrant 1.6.5のインストーラ単体では内容物に不足はありません。
「bsdtar.exe」を別途ダウンロードしてきて「gunwin32\bin」以下へ配置しても、他のいくつかのファイルが消えているので不安定な環境が残るだけです。

また、更新インストールで問題が発生する状態でも「C:\HashiCorp\Vagrant\embedded\mingw\bin」に「bsdtar.exe」があります(ファイルサイズ異なり名前は同じだが別もの)。
今回のようにインストール方法を変えて差分を確認していない人が、残っている同名のファイルへパスを通す対応をしているようです。

Vagrant 1.6.5のインストーラに「bsdtar.exe」がないわけでもなく、1.6.5で新規にパスの設定が必要になったわけでもありません。
プロダクトとして想定されているインストール結果は、1.6.5のインストーラで新規インストールした状態であり、問題が発生する更新インストールの状態ではありません。

不安定な状態に場当たり的な対応はしないで、想定されている状態にした方が良いとの判断で、現状では新規インストールが最良であると考えます。

場当たり対応をした環境で「おれの環境では発生するんだが」な問題を報告されても対応できないからねっ≧ω≦)b!!

「Vagrantで、apt-get upgradeを含んだプロビジョニングで発生するエラーについて」

はじめに

VagrantでUbuntu14.04 Serverの環境構築を行っていたところ、今まで成功していたプロビジョニングのコードが失敗するようになりました。
調査の結果、原因と解決方法を以下にまとめます。

作業環境

ホストOS

Ubuntu14.04 Desktop 64bit
Vagrant 1.6.3
VirtualBox 4.3.12.r93733

ゲストOS

Ubuntu14.04 Server 64bit

概要

  1. プロビジョニングが失敗した時の状態
  2. 原因の調査
  3. 解決方法の調査
  4. 解決方法
  5. まとめ

1.プロビジョニングが失敗した時の状態

環境構築に使用したコード

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu1404"

  config.vm.provision :shell, inline:<<-EOS
    apt-get upgrade
    apt-get -y upgrade
  EOS
end

失敗した時の出力(抜粋)

...
==> default: Package configuration┌──────────────────────────┤ Configuring grub-pc ├──────────────────────────┐│││ The GRUB boot loader was previously installed to a disk that is no││ longer present, or whose unique identifier has changed for some reason.   ││ It is important to make sure that the installed GRUB core image stays in  ││ sync with GRUB modules and grub.cfg. Please check again to make sure││ that GRUB is written to the appropriate boot devices.││││ If you're unsure which drive is designated as boot drive by your BIOS,    ││ it is often a good idea to install GRUB to all of them.││││ Note: it is possible to install GRUB to partition boot records as well,   ││ and some appropriate partitions are offered here. However, this forces    ││ GRUB to use the blocklist mechanism, which makes it less reliable, and    ││ therefore is not recommended.││││<Ok>│││└───────────────────────────────────────────────────────────────────────────┘
==> default: Failed to open terminal.64 (1:5.14-2ubuntu3.1) ...
==> default: debconf: whiptail output the above errors, giving up!
==> default: dpkg: error processing package grub-pc (--configure):
==> default:  subprocess installed post-installation script returned error exit status 255
...

2.原因の調査

失敗時の出力からプロビジョニング中にgrub-pcの設定画面を開こうとして失敗していることが分かります。
プロビジョニング中に入力を求める設定画面は使用できません。
この設定画面を回避する必要があります。

何故、grub-pcパッケージの設定画面が開くようになったか?
apt-get upgradeで更新対象になっているので、BOXファイル作成時よりgrub-pcのバージョンが上がった事が推測できます。
プロビジョニングをせずに、環境を構築しSSHで接続後、grub-pcパッケージのバージョンを確認します。

vagrant@ubuntu-1404:~$ sudo dpkg --list | grep grub
ii  grub-common                         2.02~beta2-9                  amd64        GRand Unified Bootloader (common files)
ii  grub-gfxpayload-lists               0.6                           amd64        GRUB gfxpayload blacklist
ii  grub-pc                             2.02~beta2-9                  amd64        GRand Unified Bootloader, version 2 (PC/BIOS version)
ii  grub-pc-bin                         2.02~beta2-9                  amd64        GRand Unified Bootloader, version 2 (PC/BIOS binaries)
ii  grub2-common                        2.02~beta2-9                  amd64        GRand Unified Bootloader (common files for version 2)

「2.02-beta2-9」である事が分かります。

次に手入力で、apt-get upgradeを行います。
設定画面が表示されるのですが、何故かGRUBのインストール先デバイスの問い合せです(原因は後ほど)。
構築している仮想環境のHDDは1つなので、/dev/sda (42949 MB: VBOX_HARDDISK)にチェックを入れてOKとします。


ちょっと小さいですが whiptail を使用した画面が表示されることの確認です。

apt-get upgrade後の、grub-pcパッケージのバージョンを確認します。

vagrant@ubuntu-1404:~$ sudo dpkg --list | grep grub
sudo dpkg --list | greo grub
ii  grub-common                         2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader (common files)
ii  grub-gfxpayload-lists               0.6                           amd64        GRUB gfxpayload blacklist
ii  grub-pc                             2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader, version 2 (PC/BIOS version)
ii  grub-pc-bin                         2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader, version 2 (PC/BIOS binaries)
ii  grub2-common                        2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader (common files for version 2)

「2.02-beta2-9ubuntu1」にバージョンが変わっています。

「UbuntuUpdates.org」で確認すると「2.02-beta2-9」をベースにしてパッチが当たってるようです。
http://www.ubuntuupdates.org/package/core/trusty/universe/updates/grub2

grub-pcのバージョンが上がったことは確認できました。

3.解決方法の調査

この設定画面を非表示で/dev/sdaが選択される方法を考えます。
環境変数 DEBIAN_FRONTEND に noninteractive を設定後、デフォルト値を指定してupgradeする方法を考えましが、今回の問題を解決することはできませんでした。

別の方法を調べていたところ、debianには自動化のための設定値をテキストファイルではなくデータベースで与える仕組みがあることが分かりました。
その仕組みに対応しているパッケージでは、データベースから設定値を取得するそうです。
データベースを操作するコマンドとして、debconf系のコマンドが用意されています。

設定と参照に使用するコマンドの例

設定(パッケージ/項目単位)
$ echo "set grub-pc/install_devices /dev/sda" | debconf-communicate

参照(パッケージ/項目単位)
$ echo "get grub-pc/install_devices /dev/sda" | debconf-communicate

参照(パッケージ単位)
$ debconf-show grub-pc

grub-pcパッケージは、この仕組みに対応しているようです。
検証のために、環境を再構築して upgrade 実行前に戻します。

upgrade 前にgrub-pcの設定値を確認します。

vagrant@ubuntu-1404:~$ sudo debconf-show grub-pc
  grub2/linux_cmdline_default: quiet splash
  grub-pc/kopt_extracted: false
  grub-pc/mixed_legacy_and_grub2: true
  grub-pc/postrm_purge_boot_grub: false
  grub-pc/install_devices_empty: false
  grub-pc/install_devices_disks_changed:
  grub2/linux_cmdline:
  grub-pc/hidden_timeout: true
  grub2/kfreebsd_cmdline:
  grub-pc/disk_description:
  grub2/kfreebsd_cmdline_default: quiet splash
* grub-pc/install_devices: /dev/disk/by-id/ata-VBOX_HARDDISK_VB36ceb79a-609a9c78
  grub-pc/timeout: 10
  grub-pc/chainload_from_menu.lst: true
  grub-pc/install_devices_failed_upgrade: true
  grub-pc/partition_description:
  grub2/device_map_regenerated:
  grub-pc/install_devices_failed: false

これから設定する予定の install_devices に値が設定されていますが、/dev/sda ではなく/dev/disk/by-id 以下のパスになってます。
ちょっと、/dev/disk/by-id 以下を覗いてみます。

vagrant@ubuntu-1404:~$ ll /dev/disk/by-id
total 0
drwxr-xr-x 2 root root 200 Aug  8 13:58 ./
drwxr-xr-x 4 root root  80 Aug  8 13:58 ../
lrwxrwxrwx 1 root root   9 Aug  8 13:58 ata-VBOX_HARDDISK_VBe49e1f29-745fffe8 -> ../../sda
lrwxrwxrwx 1 root root  10 Aug  8 13:58 ata-VBOX_HARDDISK_VBe49e1f29-745fffe8-part1 -> ../../sda1
lrwxrwxrwx 1 root root  10 Aug  8 13:58 ata-VBOX_HARDDISK_VBe49e1f29-745fffe8-part2 -> ../../sda2
lrwxrwxrwx 1 root root  10 Aug  8 13:58 ata-VBOX_HARDDISK_VBe49e1f29-745fffe8-part5 -> ../../sda5
lrwxrwxrwx 1 root root  10 Aug  8 13:59 dm-name-ubuntu--1404--vg-root -> ../../dm-0
lrwxrwxrwx 1 root root  10 Aug  8 13:59 dm-name-ubuntu--1404--vg-swap_1 -> ../../dm-1
lrwxrwxrwx 1 root root  10 Aug  8 13:59 dm-uuid-LVM-RCIg1EIc6CV1RnqFdUzmRFCB3TyX01FBj9TLfcBDKgLxCDXaac9gZ9FnuetB2xmM -> ../../dm-0
lrwxrwxrwx 1 root root  10 Aug  8 13:59 dm-uuid-LVM-RCIg1EIc6CV1RnqFdUzmRFCB3TyX01FBnmTl3brIQysPjdZwUTwfqdwChucxk6jW -> ../../dm-1
vagrant@ubuntu-1404:~$ 

現在の設定値に相当するパスが存在していません。
結論から言うと、BOXファイルから環境を構築する度に VBOX_HARDDISK_VB~のid部分が変わります。
データベース上の設定値は更新されません。
よって、実在しないパスとなり、grub-pcパッケージはインストール先を問い合せる画面を表示することになります。
環境変数 DEBIAN_FRONTEND でも表示を抑えられなかった理由はこれです。

では、手入力により解決できるか検証します。

設定値の登録と確認

debconf-コマンドの実行には root権限が必要となります。
プロビジョニング中はコマンドが root権限で実行されますが手入力なので sudo が必要です。

vagrant@ubuntu-1404:~$ sudo sh -c 'echo "set grub-pc/install_devices /dev/sda" | debconf-communicate'
0 value set
vagrant@ubuntu-1404:~$ sudo sh -c 'echo "get grub-pc/install_devices" | debconf-communicate'
0 /dev/sda
vagrant@ubuntu-1404:~$ 
upgradeの実行
vagrant@ubuntu-1404:~$ sudo apt-get -y upgrade

...
Setting up grub-common (2.02~beta2-9ubuntu1) ...
Setting up grub2-common (2.02~beta2-9ubuntu1) ...
Setting up grub-pc-bin (2.02~beta2-9ubuntu1) ...
Setting up grub-pc (2.02~beta2-9ubuntu1) ...
Installing for i386-pc platform.
Installation finished. No error reported.
Generating grub configuration file ...
Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported.
Found linux image: /boot/vmlinuz-3.13.0-24-generic
Found initrd image: /boot/initrd.img-3.13.0-24-generic
done
Setting up linux-firmware (1.127.5) ...
Setting up language-pack-en (1:14.04+20140707) ...
Setting up language-pack-en-base (1:14.04+20140707) ...
Generating locales...
  en_AG.UTF-8... up-to-date
  en_AU.UTF-8... up-to-date
  en_BW.UTF-8... up-to-date
  en_CA.UTF-8... up-to-date
  en_DK.UTF-8... up-to-date
  en_GB.UTF-8... up-to-date
  en_HK.UTF-8... up-to-date
  en_IE.UTF-8... up-to-date
  en_IN.UTF-8... up-to-date
  en_NG.UTF-8... up-to-date
  en_NZ.UTF-8... up-to-date
  en_PH.UTF-8... up-to-date
  en_SG.UTF-8... up-to-date
  en_US.UTF-8... up-to-date
  en_ZA.UTF-8... up-to-date
  en_ZM.UTF-8... up-to-date
  en_ZW.UTF-8... up-to-date
Generation complete.
Setting up language-pack-gnome-en (1:14.04+20140707) ...
Setting up language-pack-gnome-en-base (1:14.04+20140707) ...
Processing triggers for libc-bin (2.19-0ubuntu6.1) ...
Processing triggers for ureadahead (0.100.0-16) ...
Processing triggers for initramfs-tools (0.103ubuntu4.2) ...
update-initramfs: Generating /boot/initrd.img-3.13.0-24-generic

vagrant@ubuntu-1404:~$ sudo dpkg --list | grep grub
ii  grub-common                         2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader (common files)
ii  grub-gfxpayload-lists               0.6                           amd64        GRUB gfxpayload blacklist
ii  grub-pc                             2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader, version 2 (PC/BIOS version)
ii  grub-pc-bin                         2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader, version 2 (PC/BIOS binaries)
ii  grub2-common                        2.02~beta2-9ubuntu1           amd64        GRand Unified Bootloader (common files for version 2)

vagrant@ubuntu-1404:~$ 

4.解決方法

今回の問題に対応したプロビジョニングのコードを含んだ Vagrantfile の内容が以下になります。
debconf-communicate の一行を追加しただけですが・・・

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu1404"

  config.vm.provision :shell, inline:<<-EOS
    echo "set grub-pc/install_devices /dev/sda" | debconf-communicate
    apt-get update
    apt-get -y upgrade
  EOS
end

5.まとめ

実際に問題が発生した Vagrantfile は、もっと複雑な処理を行っていたのですが、変更していない箇所でエラーが発生していたので原因の特定に回り道をしてしまいました。
今回初めて debconf関連の情報を知ることができて良かった。
これで、この仕組みに対応している他のパッケージで同じ問題が発生しても悩む事はなさそうです♪

「Ubuntu14.04 Serverのインフラのテスト自動化(vagrant、serverspec、puppet)」|WEB+DB Press vol.80の特集記事から

はじめに

Rubyのための環境構築を行っているうちにInfrastructure as Codeへ軸が移ってきました。
構築した結果の確認をそろそろ自動化したいと考えているところで、WEB+DB Press vol.80で丁度良く特集が組まれたので、これを参考にしてserverspecの習得も始めました。
ただし、作業環境と対象環境は普段使用している環境で行いました。その結果、いくつかの違いが出たので、それをまとめました。

参考書籍

WEB+DB PRESS Vol.80

WEB+DB PRESS Vol.80

概要

作業環境

Ubuntu14.04 Desktop 64bit
Ruby 1.9.3p484
serverspec 1.7.1
VirtualBox 4.3.12 r93733
Vagrant 1.6.3

Rubyはapt-getによりインストールした場合のバージョンです。

対象環境

Ubuntu14.04 Server 64bit
Puppet 3.6.1

手順
  1. serverspecのインストール
  2. 必要なディレクトリとファイルの作成
  3. テスト駆動インフラの実践(テスト失敗)
  4. プロビジョニング後、再テストの実施(テスト成功)
  5. テスト駆動インフラの繰り返し(Ubuntuでの結果に違いあり)

1.serverspecのインストール

serverspecはgemとして公開されているので、gem installでインストールします。

stupiddog@pc001:~$ sudo gem install serverspec --no-ri --no-rdoc
Fetching: net-ssh-2.9.1.gem (100%)
Fetching: rspec-core-2.14.8.gem (100%)
Fetching: diff-lcs-1.2.5.gem (100%)
Fetching: rspec-expectations-2.14.5.gem (100%)
Fetching: rspec-mocks-2.14.6.gem (100%)
Fetching: rspec-2.14.1.gem (100%)
Fetching: highline-1.6.21.gem (100%)
Fetching: specinfra-1.13.0.gem (100%)
Fetching: serverspec-1.7.0.gem (100%)
Successfully installed net-ssh-2.9.1
Successfully installed rspec-core-2.14.8
Successfully installed diff-lcs-1.2.5
Successfully installed rspec-expectations-2.14.5
Successfully installed rspec-mocks-2.14.6
Successfully installed rspec-2.14.1
Successfully installed highline-1.6.21
Successfully installed specinfra-1.13.0
Successfully installed serverspec-1.7.0
9 gems installed

rubyをユーザローカルとしてインストールしている場合は、sudoは不要です。

2.必要なディレクトリとファイルの作成

参考書籍の特集2第2章のP59を参考にして、ディレクトリとファイルを作成します。
テスト対象となるサーバーOSを、CentOS 6.4 から Ubuntu14.04 Server に変更してるため内容に違いがあります。

作成したディレクトリ
stupiddog@pc001:~/vm$ mkdir test-driven-infra
stupiddog@pc001:~/vm$ cd test-driven-infra/
stupiddog@pc001:~/vm/test-driven-infra$ mkdir modules
stupiddog@pc001:~/vm/test-driven-infra$ mkdir -p roles/app/manifests
stupiddog@pc001:~/vm/test-driven-infra$ tree ~/vm/test-driven-infra/

/home/stupiddog/vm/test-driven-infra/
├── modules
└── roles
    └── app
        └── manifests

4 directories, 0 files
作成したファイル(環境構築部分)
  • /roles/app/manifests/init.pp
class app {}
  • provision.sh
#!/bin/sh

role=$1
puppet apply --modulepath="/vagrant/modules:/vagrant/roles" -e "include $1"
  • Vagrantfile

ベースとするBoxファイルと、最新リリースバージョンのpuppetをインストールするためのコードが特集とは異なります。
Boxファイルは、「Packerを利用したUbuntu14.04 ServerのVagrant用Boxファイルの作成」|ただいまRubyの修行中 - StupidDog's blogで作成したものを使用しています。
puppetのインストール方法については、公式サイトのInstalling Puppet: Debian and Ubuntu — Documentation — Puppet Labsを参考にしています。

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu1404"
  config.vm.box_url = "/home/stupiddog/boxs/ubuntu-14-04-x64-virtualbox.box"

  config.vm.provision :shell, inline: <<-EOF
    wget https://apt.puppetlabs.com/puppetlabs-release-$(lsb_release -cs).deb
    sudo dpkg -i puppetlabs-release-$(lsb_release -cs).deb
    sudo apt-get update
    sudo apt-get -y puppet
  EOF

  config.vm.define :app do |c|
    c.vm.provision :shell do |shell|
      shell.path = "provision.sh"
      shell.args = "app"
    end
  end
end
作成したファイル(テスト部分)

serverspec-initコマンドにより、ひな型を生成してサンプル用の不要なファイルを削除します。

stupiddog@pc001:~/vm/test-driven-infra$ serverspec-init 
Select OS type:

  1) UN*X
  2) Windows

Select number: 1

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1

Vagrant instance y/n: y 
Auto-configure Vagrant from Vagrantfile? y/n: y
 + spec/
 + spec/app/
 + spec/app/httpd_spec.rb
 + spec/spec_helper.rb
 + Rakefile
stupiddog@pc001:~/vm/test-driven-infra$ rm spec/app/httpd_spec.rb
ここまでのディレクトリとファイル
stupiddog@pc001:~/vm/test-driven-infra$ tree ~/vm/test-driven-infra/
/home/stupiddog/vm/test-driven-infra/
├── Rakefile
├── Vagrantfile
├── modules
├── provision.sh
├── roles
│   └── app
│       └── manifests
│           └── init.pp
└── spec
    ├── app
    └── spec_helper.rb

6 directories, 5 files
テスト用VMを起動
stupiddog@pc001:~/vm/test-driven-infra$ vagrant up

[実行ログの続き...]

stupiddog@pc001:~/vm/test-driven-infra$ vagrant up
Bringing machine 'app' up with 'virtualbox' provider...
==> app: Importing base box 'ubuntu1404'...
==> app: Matching MAC address for NAT networking...
==> app: Setting the name of the VM: test-driven-infra_app_1401237349890_15840
==> app: Clearing any previously set network interfaces...
==> app: Preparing network interfaces based on configuration...
    app: Adapter 1: nat
==> app: Forwarding ports...
    app: 22 => 2222 (adapter 1)
==> app: Booting VM...
==> app: Waiting for machine to boot. This may take a few minutes...
    app: SSH address: 127.0.0.1:2222
    app: SSH username: vagrant
    app: SSH auth method: private key
    app: Warning: Connection timeout. Retrying...
==> app: Machine booted and ready!
==> app: Checking for guest additions in VM...
==> app: Mounting shared folders...
    app: /vagrant => /home/stupiddog/vm/test-driven-infra
==> app: Running provisioner: shell...
    app: Running: inline script
==> app: stdin: is not a tty
==> app: Selecting previously unselected package puppetlabs-release.
==> app: (Reading database ... 59020 files and directories currently installed.)
==> app: Preparing to unpack puppetlabs-release-trusty.deb ...
==> app: Unpacking puppetlabs-release (1.0-1) ...
==> app: Setting up puppetlabs-release (1.0-1) ...
==> app: Reading package lists...
==> app: Building dependency tree...
==> app: Reading state information...
==> app: The following extra packages will be installed:
==> app:   augeas-lenses debconf-utils facter hiera libaugeas-ruby libaugeas0
==> app:   libruby1.9.1 libyaml-0-2 puppet-common ruby ruby-augeas ruby-json ruby-rgen
==> app:   ruby-shadow ruby1.9.1 virt-what
==> app: Suggested packages:
==> app:   augeas-doc augeas-tools puppet-el vim-puppet ruby-selinux libselinux-ruby1.8
==> app:   librrd-ruby1.9.1 librrd-ruby1.8 ri ruby-dev ruby1.9.1-examples ri1.9.1
==> app:   graphviz ruby1.9.1-dev ruby-switch
==> app: Recommended packages:
==> app:   rdoc
==> app: The following NEW packages will be installed:
==> app:   augeas-lenses debconf-utils facter hiera libaugeas-ruby libaugeas0
==> app:   libruby1.9.1 libyaml-0-2 puppet puppet-common ruby ruby-augeas ruby-json
==> app:   ruby-rgen ruby-shadow ruby1.9.1 virt-what
==> app: 0 upgraded, 17 newly installed, 0 to remove and 12 not upgraded.
==> app: Need to get 4,438 kB of archives.
==> app: After this operation, 21.9 MB of additional disk space will be used.
==> app: Get:1 http://apt.puppetlabs.com/ trusty/main facter amd64 2.0.1-1puppetlabs1 [62.6 kB]
==> app: Get:2 http://us.archive.ubuntu.com/ubuntu/ trusty/main libyaml-0-2 amd64 0.1.4-3ubuntu3 [48.2 kB]
==> app: Get:3 http://apt.puppetlabs.com/ trusty/main hiera all 1.3.3-1puppetlabs1 [12.0 kB]
==> app: Get:4 http://apt.puppetlabs.com/ trusty/main puppet-common all 3.6.1-1puppetlabs1 [1,015 kB]
==> app: Get:5 http://us.archive.ubuntu.com/ubuntu/ trusty/main augeas-lenses all 1.2.0-0ubuntu1 [229 kB]
==> app: Get:6 http://apt.puppetlabs.com/ trusty/main puppet all 3.6.1-1puppetlabs1 [9,322 B]
==> app: Get:7 http://us.archive.ubuntu.com/ubuntu/ trusty/main debconf-utils all 1.5.51ubuntu2 [57.4 kB]
==> app: Get:8 http://us.archive.ubuntu.com/ubuntu/ trusty/main libruby1.9.1 amd64 1.9.3.484-2ubuntu1 [2,667 kB]
==> app: Get:9 http://us.archive.ubuntu.com/ubuntu/ trusty/main ruby1.9.1 amd64 1.9.3.484-2ubuntu1 [35.6 kB]
==> app: Get:10 http://us.archive.ubuntu.com/ubuntu/ trusty/main ruby all 1:1.9.3.4 [5,334 B]
==> app: Get:11 http://us.archive.ubuntu.com/ubuntu/ trusty/universe virt-what amd64 1.13-1 [13.6 kB]
==> app: Get:12 http://us.archive.ubuntu.com/ubuntu/ trusty/main libaugeas0 amd64 1.2.0-0ubuntu1 [140 kB]
==> app: Get:13 http://us.archive.ubuntu.com/ubuntu/ trusty/main ruby-augeas amd64 0.5.0-2 [13.2 kB]
==> app: Get:14 http://us.archive.ubuntu.com/ubuntu/ trusty/universe libaugeas-ruby all 0.5.0-2 [1,394 B]
==> app: Get:15 http://us.archive.ubuntu.com/ubuntu/ trusty/main ruby-shadow amd64 2.2.0-1 [11.2 kB]
==> app: Get:16 http://us.archive.ubuntu.com/ubuntu/ trusty/main ruby-json amd64 1.8.0-1build1 [49.0 kB]
==> app: Get:17 http://us.archive.ubuntu.com/ubuntu/ trusty/main ruby-rgen all 0.6.6-1 [68.7 kB]
==> app: dpkg-preconfigure: unable to re-open stdin: No such file or directory
==> app: Fetched 4,438 kB in 4min 4s (18.2 kB/s)
==> app: Selecting previously unselected package libyaml-0-2:amd64.
==> app: (Reading database ... 59026 files and directories currently installed.)
==> app: Preparing to unpack .../libyaml-0-2_0.1.4-3ubuntu3_amd64.deb ...
==> app: Unpacking libyaml-0-2:amd64 (0.1.4-3ubuntu3) ...
==> app: Selecting previously unselected package augeas-lenses.
==> app: Preparing to unpack .../augeas-lenses_1.2.0-0ubuntu1_all.deb ...
==> app: Unpacking augeas-lenses (1.2.0-0ubuntu1) ...
==> app: Selecting previously unselected package debconf-utils.
==> app: Preparing to unpack .../debconf-utils_1.5.51ubuntu2_all.deb ...
==> app: Unpacking debconf-utils (1.5.51ubuntu2) ...
==> app: Selecting previously unselected package libruby1.9.1.
==> app: Preparing to unpack .../libruby1.9.1_1.9.3.484-2ubuntu1_amd64.deb ...
==> app: Unpacking libruby1.9.1 (1.9.3.484-2ubuntu1) ...
==> app: Selecting previously unselected package ruby1.9.1.
==> app: Preparing to unpack .../ruby1.9.1_1.9.3.484-2ubuntu1_amd64.deb ...
==> app: Unpacking ruby1.9.1 (1.9.3.484-2ubuntu1) ...
==> app: Selecting previously unselected package ruby.
==> app: Preparing to unpack .../ruby_1%3a1.9.3.4_all.deb ...
==> app: Unpacking ruby (1:1.9.3.4) ...
==> app: Selecting previously unselected package virt-what.
==> app: Preparing to unpack .../virt-what_1.13-1_amd64.deb ...
==> app: Unpacking virt-what (1.13-1) ...
==> app: Selecting previously unselected package facter.
==> app: Preparing to unpack .../facter_2.0.1-1puppetlabs1_amd64.deb ...
==> app: Unpacking facter (2.0.1-1puppetlabs1) ...
==> app: Selecting previously unselected package libaugeas0.
==> app: Preparing to unpack .../libaugeas0_1.2.0-0ubuntu1_amd64.deb ...
==> app: Unpacking libaugeas0 (1.2.0-0ubuntu1) ...
==> app: Selecting previously unselected package ruby-augeas.
==> app: Preparing to unpack .../ruby-augeas_0.5.0-2_amd64.deb ...
==> app: Unpacking ruby-augeas (0.5.0-2) ...
==> app: Selecting previously unselected package libaugeas-ruby.
==> app: Preparing to unpack .../libaugeas-ruby_0.5.0-2_all.deb ...
==> app: Unpacking libaugeas-ruby (0.5.0-2) ...
==> app: Selecting previously unselected package ruby-shadow.
==> app: Preparing to unpack .../ruby-shadow_2.2.0-1_amd64.deb ...
==> app: Unpacking ruby-shadow (2.2.0-1) ...
==> app: Selecting previously unselected package ruby-json.
==> app: Preparing to unpack .../ruby-json_1.8.0-1build1_amd64.deb ...
==> app: Unpacking ruby-json (1.8.0-1build1) ...
==> app: Selecting previously unselected package hiera.
==> app: Preparing to unpack .../hiera_1.3.3-1puppetlabs1_all.deb ...
==> app: Unpacking hiera (1.3.3-1puppetlabs1) ...
==> app: Selecting previously unselected package ruby-rgen.
==> app: Preparing to unpack .../ruby-rgen_0.6.6-1_all.deb ...
==> app: Unpacking ruby-rgen (0.6.6-1) ...
==> app: Selecting previously unselected package puppet-common.
==> app: Preparing to unpack .../puppet-common_3.6.1-1puppetlabs1_all.deb ...
==> app: Unpacking puppet-common (3.6.1-1puppetlabs1) ...
==> app: Selecting previously unselected package puppet.
==> app: Preparing to unpack .../puppet_3.6.1-1puppetlabs1_all.deb ...
==> app: Unpacking puppet (3.6.1-1puppetlabs1) ...
==> app: Processing triggers for man-db (2.6.7.1-1) ...
==> app: Processing triggers for ureadahead (0.100.0-16) ...
==> app: ureadahead will be reprofiled on next reboot
==> app: Setting up libyaml-0-2:amd64 (0.1.4-3ubuntu3) ...
==> app: Setting up augeas-lenses (1.2.0-0ubuntu1) ...
==> app: Setting up debconf-utils (1.5.51ubuntu2) ...
==> app: Setting up virt-what (1.13-1) ...
==> app: Setting up libaugeas0 (1.2.0-0ubuntu1) ...
==> app: Setting up ruby (1:1.9.3.4) ...
==> app: Setting up facter (2.0.1-1puppetlabs1) ...
==> app: Setting up ruby-rgen (0.6.6-1) ...
==> app: Setting up ruby1.9.1 (1.9.3.484-2ubuntu1) ...
==> app: Setting up libruby1.9.1 (1.9.3.484-2ubuntu1) ...
==> app: Setting up ruby-augeas (0.5.0-2) ...
==> app: Setting up libaugeas-ruby (0.5.0-2) ...
==> app: Setting up ruby-shadow (2.2.0-1) ...
==> app: Setting up ruby-json (1.8.0-1build1) ...
==> app: Setting up hiera (1.3.3-1puppetlabs1) ...
==> app: Setting up puppet-common (3.6.1-1puppetlabs1) ...
==> app: Setting up puppet (3.6.1-1puppetlabs1) ...
==> app:  * Starting puppet agent
==> app: puppet not configured to start, please edit /etc/default/puppet to enable
==> app:    ...done.
==> app: Processing triggers for libc-bin (2.19-0ubuntu6) ...
==> app: Processing triggers for ureadahead (0.100.0-16) ...
==> app: Running provisioner: shell...
    app: Running: /tmp/vagrant-shell20140528-15607-1gkjyx1.sh
==> app: stdin: is not a tty
==> app: Warning: Setting templatedir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations
==> app:    (at /usr/lib/ruby/vendor_ruby/puppet/settings.rb:1071:in `each')
==> app: Notice: Compiled catalog for ubuntu-1404.vagrantup.com in environment production in 0.02 seconds
==> app: Notice: Finished catalog run in 0.01 seconds

※ 短時間でsudo apt-get updateを繰り返し過ぎるとリポジトリへのアクセスが遅くなるような…気のせいかな。

3.テスト駆動インフラの実践(テスト失敗)

テスト結果を見やすくするための設定ファイルも作成します。

RSpec設定ファイル
--color
--format d
テストコードの作成
  • spec/app/ntp_spec.rb
require 'spec_helper'

describe package('ntp') do
  if { should be_installed }
end
テストを実行
stupiddog@pc001:~/vm/test-driven-infra$ rake spec
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb

Package "ntp"
  should be installed (FAILED - 1)

Failures:

  1) Package "ntp" should be installed
     Failure/Error: it { should be_installed }
       sudo dpkg-query -f '${Status}' -W ntp | grep -E '^(install|hold) ok installed$'
       expected Package "ntp" to be installed
     # ./spec/app/ntp_spec.rb:4:in `block (2 levels) in <top (required)>'

Finished in 3.9 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/app/ntp_spec.rb:4 # Package "ntp" should be installed
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb failed

テストの失敗を確認。チェックに使用しているコマンドも表示されています。

4.プロビジョニング後、再テストの実施(テスト成功)

Puppetマニフェストの作成(ntpモジュール)
  • module/ntp/manifests/init.pp
class ntp {
  package { 'ntp':
    ensure => installed
  }
}
appロールへのモジュール呼び出し追加
class app {
  include ntp
}
Puppetマニフェストの適用
stupiddog@pc001:~/vm/test-driven-infra$ vagrant provision
==> app: Running provisioner: shell...
    app: Running: inline script
==> app: stdin: is not a tty
==> app: (Reading database ... 62069 files and directories currently installed.)
==> app: Preparing to unpack puppetlabs-release-trusty.deb ...
==> app: Unpacking puppetlabs-release (1.0-1) over (1.0-1) ...
==> app: Setting up puppetlabs-release (1.0-1) ...
==> app: Reading package lists...
==> app: Building dependency tree...
==> app: Reading state information...
==> app: puppet is already the newest version.
==> app: 0 upgraded, 0 newly installed, 0 to remove and 22 not upgraded.
==> app: Running provisioner: shell...
    app: Running: /tmp/vagrant-shell20140609-5527-7v3h9v.sh
==> app: stdin: is not a tty
==> app: Warning: Setting templatedir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations
==> app:    (at /usr/lib/ruby/vendor_ruby/puppet/settings.rb:1071:in `each')
==> app: Notice: Compiled catalog for ubuntu-1404.vagrantup.com in environment production in 0.19 seconds
==> app: Notice: /Stage[main]/Ntp/Package[ntp]/ensure: ensure changed 'purged' to 'present'
==> app: Notice: Finished catalog run in 12.68 seconds
テストの実行
stupiddog@pc001:~/vm/test-driven-infra$ rake spec
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb

Package "ntp"
  should be installed

Finished in 3.96 seconds
1 example, 0 failures

テストの成功を確認。これで、テスト駆動インフラの入り口に着ました。

5.テスト駆動インフラの繰り返し(Ubuntuでの結果に違いあり)

テストの追加

ntpdサービスが自動起動するようになっている(be_enabled)と、起動している(be_running)をテストするコードを追加する。

  • spec/app/ntp_spec.rb
require 'spec_helper'

describe package('ntp') do
  it { should be_installed }
end

describe service('ntpd') do
  it { should be_enabled }
  it { should be_running }
end
テストの実施(テスト失敗)
stupiddog@pc001:~/vm/test-driven-infra$ rake spec
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb

Package "ntp"
  should be installed

Service "ntpd"
  should be enabled (FAILED - 1)
  should be running

Failures:

  1) Service "ntpd" should be enabled
     Failure/Error: it { should be_enabled }
       sudo ls /etc/rc3.d/ | grep -- '^S..ntpd' || sudo grep 'start on' /etc/init/ntpd.conf
       expected Service "ntpd" to be enabled
     # ./spec/app/ntp_spec.rb:8:in `block (2 levels) in <top (required)>'

Finished in 4.06 seconds
3 examples, 1 failure

Failed examples:

rspec ./spec/app/ntp_spec.rb:8 # Service "ntpd" should be enabled
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb failed

新しく追加したテストの失敗を確認。
ここで、テスト結果を見ると書籍と異なることが分かります。
失敗しているのは、「ntpdサービスが自動起動するようになっている(be_enabled)」のみで、「起動している(be_running)」は成功しています。

これは、Ubuntuのaptでntpをインストールした場合、自動起動の設定も合わせて行われるためです。
念のためテスト対象の仮想環境でntpサービスが起動していることを確認します。

stupiddog@pc001:~/vm/test-driven-infra$ vagrant ssh
Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.13.0-24-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
Last login: Mon May 19 10:27:04 2014 from 10.0.2.2

vagrant@ubuntu-1404:~$ ps aux | grep ntpd | grep -v grep
ntp       2026  0.0  0.2  31444  2056 ?        Ss   07:11   0:00 /usr/sbin/ntpd -p /var/run/ntpd.pid -g -u 105:113
vagrant@ubuntu-1404:~$ 

ntpサービス(ntpd)が起動しています。
では何故「ntpdサービスが自動起動するようになっている(be_enabled)」が失敗となったのか。
もう一度、テスト結果を確認すると失敗しているテストのコマンドが出力されていることが分かります。

  1) Service "ntpd" should be enabled
     Failure/Error: it { should be_enabled }
       sudo ls /etc/rc3.d/ | grep -- '^S..ntpd' || sudo grep 'start on' /etc/init/ntpd.conf
       expected Service "ntpd" to be enabled
     # ./spec/app/ntp_spec.rb:8:in `block (2 levels) in <top (required)>'

二種類のコマンドのどちらかが成功した場合にテスト成功となるようです。

  • sudo ls /etc/rc3.d/ | grep -- '^S..ntpd'
  • sudo grep 'start on' /etc/init/ntpd.conf

まず、コマンドを手入力してみます。

vagrant@ubuntu-1404:~$ sudo ls /etc/rc3.d/ | grep -- '^S..ntpd'
vagrant@ubuntu-1404:~$ sudo ls /etc/rc3.d/
README	S20rsync  S20virtualbox-guest-utils  S21puppet	S23ntp	S70dns-clean  S70pppd-dns  S99grub-common  S99ondemand	S99rc.local

vagrant@ubuntu-1404:~$ sudo grep 'start on' /etc/init/ntpd.conf
grep: /etc/init/ntpd.conf: No such file or directory

結果から、/etc/init/ntpd.confファイルは存在しないようです。
そこでもう一つのコマンドを確認すると、起動スクリプトの有無をチェックしていることが分かります。
しかし、実際に存在しているntpの起動スクリプト名が「S23ntpd」ではなく「S23ntp」なので条件に一致しません。

結果の考察と対応

serverspecでのテストで、成功となるケースが失敗となってしまいました。
ntpサービスがファイル配置や名前を頻繁に変えてしまうのが原因だと思います。
これで、serverspecを使用しない~ではなく、OSSでありインフラテストの仕組みを提供しているのだから自分の環境で正しいテストが出来るようにテストコードを書き直します。

どうやってコマンドが生成されているか

テストが失敗した場合に、テストに使用しているコマンドが出力されるので敢えて存在しないサービス名を指定してみました。

  • spec/app/ntp_spec.rb
stupiddog@pc001:~/vm/test-driven-infra$ cat spec/app/ntp_spec.rb
require 'spec_helper'

describe package('ntp') do
  it { should be_installed }
end

describe service('aaa') do
  it { should be_enabled }
  it { should be_running }
end

stupiddog@pc001:~/vm/test-driven-infra$ rake spec
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb

Package "ntp"
  should be installed

Service "aaa"
  should be enabled (FAILED - 1)
  should be running (FAILED - 2)

Failures:

  1) Service "aaa" should be enabled
     Failure/Error: it { should be_enabled }
       sudo ls /etc/rc3.d/ | grep -- '^S..aaa' || sudo grep 'start on' /etc/init/aaa.conf
       expected Service "aaa" to be enabled
     # ./spec/app/ntp_spec.rb:8:in `block (2 levels) in <top (required)>'

  2) Service "aaa" should be running
     Failure/Error: it { should be_running }
       sudo ps aux | grep -w -- aaa | grep -qv grep
       expected Service "aaa" to be running
     # ./spec/app/ntp_spec.rb:9:in `block (2 levels) in <top (required)>'

Finished in 4.02 seconds
3 examples, 2 failures

Failed examples:

rspec ./spec/app/ntp_spec.rb:8 # Service "aaa" should be enabled
rspec ./spec/app/ntp_spec.rb:9 # Service "aaa" should be running
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb failed

describe service()に指定した名前が、そのまま使用されるようです。
そこで、テストコードを次のように書き直してテストが正常に行われるようにしました。

  • spec/app/ntp_spec.rb
require 'spec_helper'

describe package('ntp') do
  it { should be_installed }
end

describe service('ntp') do
  it { should be_enabled }
end

describe service('ntpd') do
  it { should be_running }
end
テストの実施
stupiddog@pc001:~/vm/test-driven-infra$ rake spec
/usr/bin/ruby1.9.1 -S rspec spec/app/ntp_spec.rb

Package "ntp"
  should be installed

Service "ntp"
  should be enabled

Service "ntpd"
  should be running

Finished in 4.02 seconds
3 examples, 0 failures

これで対象環境のテストコードと、ntpサービスを導入したUbuntu14.04 Server環境を作るためのコードが作成できました。

まとめ

今回、テストコードを変更してテストを通す結果となりました。
テストが成功するようにテストコードを変更したことになりますが、手本とした書籍のコードがたまたま存在していただけです。
ゼロから構築することを考えた場合、serverspecでの記述方法だけでなく、どうやってチェックしているかを知らないと新しい要素のテストコードを記述できないことが分かりました。
実施してみて注意することが分かったので、今後は他の条件のテストも行って習得度を上げていきます。

「Vagrant initで最小限のコメントだけが含まれたVagrantfileを作成する」|ただいまRubyの修行中

はじめに

vagrant intiで作成されるVagrantfileのコード部分は小さいのでゼロから入力してもいいのですが、少しでも効率を上げるなら利用したいところです。しかし、作成されたVagrantfileはコメントが多すぎてとてもベースとして利用できません。

そこで、最小限のコメントのVagrantfileを作成するオプションを指定します。

方法

公式サイトのドキュメントにある--minimalを指定します。
vagrant init - Command-Line Interface - Vagrant Documentation

ソースコードを確認すると-mとする短縮があるので、実際は、こちらを指定します。

コマンド
stupiddog@pc001:~/vm/a003$ vagrant init ubuntu1404 ~/boxs/ubuntu-14-04-x64-virtualbox.box -m
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.
作成されたVagrantfileの内容
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu1404"
  config.vm.box_url = "/home/stupiddog/boxs/ubuntu-14-04-x64-virtualbox.box"
end

まとめ

これで少し効率アップです。

「Packerを利用したUbuntu14.04 ServerのVagrant用Boxファイルの作成」|ただいまRubyの修行中

はじめに

Ubuntu 14.04 Serverが公開されたので新しい環境を作成することにしました。
まだ、適当なUbuntu 14.04 ServerのBoxファイルが無いので今後のために自作する方法を習得しておきます。
Packerを利用する方法を探していたら必要な設定ファイル一式を公開しているサイトを見つけたので、有難く利用させて頂きます。

参照サイト
packer - Ubuntu 14.04 を Vagrant に準備する - Qiita

時雨堂さんのPackerテンプレート
shiguredo/packer-templates · GitHub

概要

作業環境
Ubuntu 14.04 Desktop 64bit
VirtualBox 4.3.12r93733
Vagrant 1.6.2
Packer v0.6.0
手順概要
  1. VirtualBoxのインストール
  2. Vagrantのインストール
  3. Packerのインストール
  4. Packerテンプレートの取得
  5. Boxファイルの生成
  6. 生成したBoxファイルで仮想環境の構築とssh接続テスト

1.VirtualBoxのインストール

公式サイトからVirtualBoxインストーラーをダウンロードする。
Downloads – Oracle VM VirtualBox
(ファイル「virtualbox-4.3_4.3.10-93012~Ubuntu~raring_amd64.deb」)


stupiddog@pc001:~$ dpkg -i ~/Downloads/virtualbox-4.3_4.3.12-93733~Ubuntu~raring_amd64.deb
...
stupiddog@pc001:~$ vboxmanage -v
4.3.12r93733

2.Vagrantのインストール

公式サイトからVagrantインストーラーをダウンロードする。
Download Vagrant - Vagrant
(ファイル「vagrant_1.6.2_x86_64.deb」)

stupiddog@pc001:~$ sudo dpkg -i ~/Downloads/vagrant_1.6.2_x86_64.deb 
...
stupiddog@pc001:~$ vagrant -v
Vagrant 1.6.2

3.Packerのインストール

公式サイトからPackerのパッケージをダウンロードする。
Downloads - Packer
(ファイル「0.6.0_linux_amd64.zip」)

公式サイトのドキュメントに従ってファイルを展開しパスを通します。
Install Packer - Packer

今回はユーザーのホームディレクトリへ配置しました。

stupiddog@pc001:~$ unzip ~/Downloads/0.6.0_linux_amd64.zip -d ~/packer
...
stupiddog@pc001:~$ echo 'export "$HOME/packer:$PATH"' >> ~/.bashrc
stupiddog@pc001:~$ source ~/.bashrc
stupiddog@pc001:~$ packer -v
Packer v0.6.0

4.Packerテンプレートの取得

Githubからファイルを取得(clone)する

stupiddog@pc001:~$ git clone https://github.com/shiguredo/packer-templates.git ~/packer-templates
Cloning into '/home/stupiddog/packer-templates'...
remote: Reusing existing pack: 133, done.
remote: Total 133 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (133/133), 21.33 KiB | 0 bytes/s, done.
Resolving deltas: 100% (55/55), done.
Checking connectivity... done.

5.Boxファイルの生成

取得したPackerテンプレートの中にはバージョン毎にファイルが分けられています。
テンプレートは、VirtualBox以外に VMWareにも対応していますが今回は、VirtualBoxのBoxイメージのみを生成します。
Packerコマンドのオプションとして、-only=virtualbox-isoを指定して実行します。

stupiddog@pc001:~$ cd ~/packer-templates/ubuntu-14.04
stupiddog@pc001:~$ packer build -only=virtualbox-iso template.json
stupiddog@pc001:~/packer-templates/ubuntu-14.04$ packer build -only=virtualbox-iso template.json
virtualbox-iso output will be in this color.

==> virtualbox-iso: Downloading or copying Guest additions checksums
    virtualbox-iso: Downloading or copying: http://download.virtualbox.org/virtualbox/4.3.12/SHA256SUMS
==> virtualbox-iso: Downloading or copying Guest additions
    virtualbox-iso: Downloading or copying: http://download.virtualbox.org/virtualbox/4.3.12/VBoxGuestAdditions_4.3.12.iso
...
    virtualbox-iso (vagrant): Compressing: metadata.json
    virtualbox-iso (vagrant): Compressing: packer-virtualbox-iso-disk1.vmdk
Build 'virtualbox-iso' finished.

==> Builds finished. The artifacts of successful builds are:
--> virtualbox-iso: 'virtualbox' provider box: ubuntu-14-04-x64-virtualbox.box
drwxrwxr-x 5 stupiddog stupiddog      4096  5月 21 22:28 ./
drwxrwxr-x 9 stupiddog stupiddog      4096  5月 21 20:46 ../
drwxrwxr-x 2 stupiddog stupiddog      4096  5月 21 20:46 http/
drwxr-xr-x 2 stupiddog stupiddog      4096  5月 21 21:07 packer_cache/
drwxrwxr-x 2 stupiddog stupiddog      4096  5月 21 20:46 scripts/
-rw-rw-r-- 1 stupiddog stupiddog      4108  5月 21 20:46 template.json
-rw-rw-r-- 1 stupiddog stupiddog 511869354  5月 21 22:28 ubuntu-14-04-x64-virtualbox.box

Ubuntu14.04 ServerのISOイメージを公式サイトからダウンロードし、エラーが無ければ生成されたBoxファイルがカレントディレクトリに作成されます。
(ファイル「ubuntu-14-04-x64-virtualbox.box」)

6.生成したBoxファイルで仮想環境の構築とssh接続テスト


作業ディレクトリ「~/vm/a001」を作成し、Vagrantfileを下記の内容で配置してvagrant upコマンドを実行します。

仮想環境用のディレクトリを作成する

管理用ディレクトリを作成して、そこをカレントディレクトリとします。

stupiddog@pc001:~$ mkdir -p ~/vm/a001
stupiddog@pc001:~$ cd ~/vm/a001
stupiddog@pc001:~/vm/a001$ tree ~/vm/a001
/home/stupiddog/vm/a001
└── vagrantfile

0 directories, 1 file
Vagrantfileを作成する

管理用ディレクトリに以下の内容で「vagrantfile」ファイルを作成します。

VAGRANT_API_VERSION = "2"

Vagrant.configure(VAGRANT_API_VERSION) do |config|
  config.vm.box      = "ubuntu1404"
  config.vm.box_url  = "~/packer-tamplates/ubuntu-14-04-x64-virtualbox.box"
  config.vm.hostname = "a001.local"
  config.vm.network "private_natwork", ip:"192.168.100.100"
end
仮想環境を構築する
stupiddog@pc001:~/vm/a001$ cat vagrantfile 
VAGRANT_API_VERSION = "2"

Vagrant.configure(VAGRANT_API_VERSION) do |config|
  config.vm.box      = "ubuntu1404"
  config.vm.box_url  = "~/packer-tamplates/ubuntu-14-04-x64-virtualbox.box"
  config.vm.hostname = "a001.local"
  config.vm.network "private_network", ip:"192.168.100.100"
end

stupiddog@pc001:~/vm/a001$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu1404'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: a001_default_1400689238903_18288
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
    default: Adapter 2: hostonly
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection timeout. Retrying...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
    default: /vagrant => /home/stupiddog/vm/a001
stupiddog@pc001:~/vm/a001$ vagrant ssh
Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.13.0-24-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
Last login: Mon May 19 10:27:04 2014 from 10.0.2.2
vagrant@a001:~$ 

まとめ

PackerがOSを非対話型でインストールするために、Redhat系ではKickStartDebian系ではpreseedを利用します。
UbuntuDebian系なのでpreseedとなるのですが、このpreseedの入門的ドキュメントが無く敷居が高すぎました。
そこへ、動いて試せるテンプレートを時雨堂さんの所で公開してもらえたお陰でぴょーんと!!
サンプルではなく実用的な動く手本なので、今後は、このテンプレートの詳細を調べることにします。
これで、外部で作成されたBoxファイルだから信用がうんぬんと言うちゃちゃとはオサラバです。