令和の時代に Perl で CSV ファイルを操作する

はじめに

とあることから久しぶりに CSV ファイルの生成を行う必要がありましたので、忘備録としてこの文章を作成しています。

また今どき Perl を使ったものなのですが、検索すると古めの情報が多かったので少しだけ頑張ってみました。

コピペで実行可能な内容にしていますので、困っている人の役に立てれば幸いです。

背景

CSV ファイルは「カンマ区切り」と単純な形式と思われますが、実際は実装依存である場合が多いため、意外と面倒な事が多いです。

このため、自前で頑張って実装するより、よくデバッグされたライブラリを使用したほうが確実です。

また RFC は存在していますが、参考程度にしておいたほうが良いです。 https://datatracker.ietf.org/doc/html/rfc4180

ちなみに「Microsoft Excel で処理ができる形式」が無難かもしれません。 これは非技術者層でデータの生成手段として、Excel が多く使われている傾向があるためです。

※ 今日日は JSONYAML などが多く使われますが、これらは手書きが面倒でプログラムから生成する前提となっています。

想定される環境

環境は下記の表とします。

項目 内容 備考
OS Ubuntu 22.04.5 LTS いわゆる UNIX ぽい環境
シェル zsh Bourne Shell
言語 perl 好み

OS に依存した部分は少ないはずなので、他のいわゆる UNIX 環境でも同様の手順でできるはずです。

今日日 perl なのは個人的な好みで「1ダースほどのプログラム言語経験から、比較的面倒が少ない」からです。

※ 他の言語はナウい情報がネットに溢れているので、そちらを参照してください。WEB 系なら PHP でも良いかも。

必要なものをインストールする

perlbrew と cpanm とあわせてホームディレクトリ以下にインストールする方法を推奨します。

perlbrew のインストール

perlbrew をインストールします。 https://perlbrew.pl/

% curl -L https://install.perlbrew.pl | sh

インストールできたら、perlbrew 用の $PATH の設定を行います。

% echo 'source ~/perl5/perlbrew/etc/bashrc' >> ~/.zshrc
% source ~/.zshrc

$PATH に $HOME/perl5/perlbrew/bin が追加され、perlbrew コマンドが実行できることを確認します。

% echo $PATH
/home/popopo/perl5/perlbrew/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
% perlbrew version
/home/popopo/perl5/perlbrew/bin/perlbrew  - App::perlbrew/1.01

まずは perlbrew available でインストール可能な perl のバージョン一覧を確認します。

% perlbrew available
# perl
   perl-5.40.0
   perl-5.38.2
   perl-5.36.3
   perl-5.34.3
   perl-5.32.1
   perl-5.30.3
   perl-5.28.3
   perl-5.26.3
   perl-5.24.4
   perl-5.22.4
   perl-5.20.3
   perl-5.18.4
   perl-5.16.3
   perl-5.14.4
   perl-5.12.5
   perl-5.10.1
    perl-5.8.9
    perl-5.6.2

どのバージョンを使うのは好みで良いですが、コアモジュールの構成が異なることがあります。

とりあえず普段使いの FreeBSD における FreeBSD Ports でのデフォルトでは 5.36 のようなので、それに準拠してみます。

https://cgit.freebsd.org/ports/commit/?id=039a6a2aed7b227b06e07147e5246d04f0a004fb

% perlbrew install perl-5.36.3

次に OS 側にインストールされた perl から切り替えます。

現状は OS 側にインストールされた perl を使用している
% perl -V:version
version='5.34.0';
% which perl
/usr/bin/perl

perlbrew でインストールされた perl の一覧を確認する
% perlbrew list
  perl-5.36.3

perlbrew switch で使用する perl を切り替える
% perlbrew switch perl-5.36.3

perl コマンドで実行されるものが perlbrew でインストールしたものに切り替わっていることを確認する
% perl -V:version
version='5.36.3';
% which perl
/home/popopo/perl5/perlbrew/perls/perl-5.36.3/bin/perl

cpanm のインストール

cpanm を導入します。

今回は perlbew で $HOME 以下にインストールされた環境への導入になるため、特権は不要です。

% curl -L http://cpanmin.us | perl - App::cpanminus
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  295k  100  295k    0     0  1720k      0 --:--:-- --:--:-- --:--:-- 1725k
--> Working on App::cpanminus
Fetching http://www.cpan.org/authors/id/M/MI/MIYAGAWA/App-cpanminus-1.7048.tar.gz ... OK
Configuring App-cpanminus-1.7048 ... OK
Building and testing App-cpanminus-1.7048 ... OK
Successfully installed App-cpanminus-1.7048
1 distribution installed
% which cpanm
/home/popopo/perl5/perlbrew/perls/perl-5.36.3/bin/cpanm

Text::CSV_XS のインストール

次に Text::CSV_XS をインストールします。

% cpanm Text::CSV_XS
--> Working on Text::CSV_XS
Fetching http://www.cpan.org/authors/id/H/HM/HMBRAND/Text-CSV_XS-1.58.tgz ... OK
Configuring Text-CSV_XS-1.58 ... OK
Building and testing Text-CSV_XS-1.58 ... OK
Successfully installed Text-CSV_XS-1.58
1 distribution installed

インストールしたモジュールが使えるかを確認します。

% perl -MText::CSV_XS -le 'print $Text::CSV_XS::VERSION'
1.58

コンパイラが使えない環境などへの移植性を考慮して Text::CSV をインストールしても良いです。

※ ただし、Text::CSV は Text::CSV_XS が存在しない環境では Text::CSV_PP を使います。これは XS と比較すると動作はかなり遅いです。

Text::CSV_XS を用いたサンプルコード

実際に CSV ファイルを扱うサンプルコードを書いてみます。

https://metacpan.org/pod/Text::CSV

CSV データの用意

使用するデータは下記 URL から取得できる郵便番号とします。

https://www.post.japanpost.jp/zipcode/dl/utf-zip.html

% mkdir ~/src/csvtest
% cd ~/src/csvtest
% curl -L -O https://www.post.japanpost.jp/zipcode/dl/utf/zip/utf_ken_all.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1984k  100 1984k    0     0  7292k      0 --:--:-- --:--:-- --:--:-- 7322k
% ls
utf_ken_all.zip
% unzip utf_ken_all.zip
Archive:  utf_ken_all.zip
  inflating: utf_ken_all.csv

CSV データの読み込み

読み込んだ郵便番号データから任意の都道府県のものだけを出力する処理とします。

要件は下記となります。

  • 引数として下記を受ける
    • 読み込み対象となる郵便番号データのファイルパス
    • 取り出したい都道府県名を指定できる
  • 出力されるファイル名は下記とする
  • 出力されたファイルは Microsoft Excel で読み込める
    • そのままダブルクリックで開くには BOM が存在している必要がある

実際のサンプルコードは下記になります。

#! /usr/bin/env perl
#
use v5.36;
use utf8;
binmode STDOUT, ":utf8";
use Text::CSV_XS;
# UTF で文字化けするので上書きする
use Data::Dumper;
{
    package Data::Dumper;
    {
        no warnings;
        sub qquote { return shift; }
    }
}
$Data::Dumper::Useperl = 1;

## サブルーチン
# usage
sub usage {
    say STDERR @_;
    say "usage: $0 <csvfile> <prefecture>";
    exit -1;
}

## メイン
# CSV ファイルの指定
my $CSV_FILE = $ARGV[0] || "";
if ( ! -f $CSV_FILE ) {
    usage( "Error! csv file not set." );
}
# 取り出したい都道府県
my $TARGET_PREF= $ARGV[1] || "";
if ( ! $TARGET_PREF ) {
    usage( "Error! target prefecture not set." );
}
utf8::decode( $TARGET_PREF );

my $csv = Text::CSV_XS->new ( { binary => 1, auto_diag => 1 } );
open my $fh, "<:encoding(utf8)", $CSV_FILE or die "$CSV_FILE: $!";

my @results;
while ( my $row = $csv->getline( $fh ) ) {
    # 7. 都道府県名 ………… 漢字(コード順に掲載)
    $row->[6] eq $TARGET_PREF or next;
    push( @results, $row );
}
close( $fh );

# デバッグ用
# say Dumper( @results );

# 書き出し
my $output_filename = "postcode_$TARGET_PREF.csv";
open( my $nfh, ">:encoding(utf8)", $output_filename ) or die "$output_filename: $!";
# Excel でそのままダブルクリックで開きたい場合は BOM を付ける (他に良い方法があるはず)
print $nfh "\x{FEFF}";
$csv->say( $nfh, $_ ) for @results;
close( $fh );

exit;

下記のように実行します。

北海道のみ取り出す
% ./read_csv.pl utf_ken_all.csv 北海道

兵庫県のみ取り出す
% ./read_csv.pl utf_ken_all.csv 兵庫県

次に出力結果が期待したものになっているかを雑に確認します。

  • grep -c で検索単語のカウント数を出力する
    • 元データの utf_ken_all.csv に含まれる対象の都道府県名の出現数を求める
  • wc -l で行数を出力する
    • 出力結果の行数を求める
  • これらの実行結果が同一であれば、出力されたデータは正しいと判断する

※ 他にも方法はありそうなのですが、それはお好みで

% grep -c 北海道 utf_ken_all.csv
8190
% wc -l postcode_北海道.csv
8190 postcode_北海道.csv

% grep -c 兵庫県 utf_ken_all.csv
5228
% wc -l postcode_兵庫県.csv
5228 postcode_兵庫県.csv

ちなみに下記のように間違ったデータが含まれていないことを確認しても良い
% grep -vc 北海道 postcode_北海道.csv
0
% grep -vc 兵庫県 postcode_兵庫県.csv
0

まとめ

令和の時代ですが、Perl と Text::CSV を用いた CSV ファイルの処理について記載しました。

DX などでデータの抽出や連携で必要になり無理難題を言われた方の参考になれば幸いです。

pf ユーザのための ufw + ipset 運用

はじめに

気がついたら 13年ぐらいはてなブロクを放置していました。

いや、Twitter で良いとは思っているのですが、細かい話などになるとこっちの方かな、と思い出した次第です。

で、相変わらずニッチな部分の記録の吐き出しとなります。

ufw は面倒くさいし、iptables もさらに面倒くさい

ubuntu ホストでファイヤーウォールの設定となると ufw になりますが、これがかなり面倒です。

  • ブロックしたい IP アドレスをその都度 ufw で設定しなければならない
  • 結果として iptables のエントリがグチャグチャになる
  • この管理が面倒くさい

これを pf で言うところの外部ファイルに設定し table を定義する形式したいです。

table <blocklist> persist file "/etc/pf.blocklist"
(snip)
block in quick from <blocklist>

こんな感じで気に入らないネットワーク情報を /etc/pf.blocklist に突っ込む → pfctl reload する運用に近い感じにしたいのです。

要は「ファイルで設定をばら撒ける」みたいな感じ。(FreeBSDLinux が混在しているので面倒なのです)

また、外部のブラックリストを拾ってきて適用する、みたいな運用を想定しています。

注意点

ufw は設定済みです。

  • 管理用ネットワークからの接続は許可
  • 公開サービスのポートのみ接続を許可
  • 普通? のファイヤーウォール設定は済

どうするか

ipset を使うと良いみたい。

ubuntuforums.org

/etc/ufw/after.init 内で設定してみる、感じです。

github.com

これがそのまま使えそうです。

導入

導入は ufw-ipset-blocklist-autoupdate の README そのままで良いです。

ただし、ローカルのファイルを読み込めるようにしたいので、下記のように変更しています。

% git diff update-ip-blocklists.sh
diff --git a/update-ip-blocklists.sh b/update-ip-blocklists.sh
index f40ee03..17fe7be 100755
--- a/update-ip-blocklists.sh
+++ b/update-ip-blocklists.sh
@@ -200,7 +200,7 @@ function update_blocklist() {
     log "Updating blacklist '$1' ..."
     log_verbose "Downloading blocklist '$1' from: $2 ..."
     local tempfile=$(mktemp "/tmp/blocklist.$1.XXXXXXXX")
-    wget -q -O "$tempfile" "$2"
+    curl -s -o "$tempfile" "$2"

     # Check downloaded list
     linecount=$(cat "$tempfile" | wc -l)

wget コマンドは file プロトコルに対応していないため、curl コマンドに変更しています。

これにより、下記のようにローカルにあるファイルから読み込めるようになります。

% sudo /path/to/update-ip-blocklists.sh -l "match-set-name file:///path/to/iplist"

動作確認

これはそのまま ipset と iptables の実行結果を確認します。

とりあえず、ufw-ipset-blocklist-autoupdate の README にある blocklist.de のリストが適切です。 (ここのリストは問題が少ないので、実運用に適用しても良いです)

% sudo ipset l -n
bl-blocklist-inet

% sudo iptables -L -n | grep ^DROP | grep match-set
DROP       all  --  0.0.0.0/0            0.0.0.0/0            match-set bl-blocklist-inet src

実際にブロックされるか試してみたい場合は下記のようにしてみます。

% echo "ブロックされたい IP アドレス" > /path/to/match-set-test
% sudo  /path/to/update-ip-blocklists.sh -l "match-set-test file:///path/to/match-set-test"

ブロックされたい IP アドレスのホストから通信をしてみて確立しなければ可とします。 (curl とかで。あわせて tcpdump などでパケットダンプを見るもの良いです)

少し調整

ログの出力が多すぎるて困る場合は、下記のようにします。 あまり良くない方法なのですが、単純に /etc/ufw/after{6}.init にて LOG エントリを設定しないようにするぐらいしか思いつきませんでした。

         $IPSET_BIN restore -! < "$f"
         iptables -I INPUT -m set --match-set "$listname" src -j DROP
        #iptables -I INPUT -m set --match-set "$listname" src -j LOG --log-prefix "[UFW BLOCK $listname] "

まとめ

「ファイルのばらまきでやりたい」てな人はこういうやり方もあるよ、みたいな感じです。

また、外部ブラックリストの採用を検討されている方の参考になると幸いです。

(おまけ) ブラックリストはどれを使えば良いのよ

どれでも良いですが、ポリシが明確である所が良いです。

  • 登録される理由が提示されている
  • 解除できる理由が提示されている

この解除できるというのが重要です。

また、近年においてはクラウドなどでネットワークのいわゆる「使いまわし」が多いため、長期のブロックをしていると何らかの問題が発生することもあります。 (再利用までの期間が短い + クリーニングをしない事業者もいます)

特に仕事でやってる方は気をつけたほうが良いです。

また、この場合は必ずホワイトリスト運用もあわせて行ってください。

Source Dedicated Server (SRCDS)

Team Fortress 2 が play for free になったので、Ded鯖を立ててみることにしてみました。
Quake 系はガンガン立てていたけれど、Source 系はさっぱりだし良い機会ということで。

Dedicated Server

ここでは主に PC ゲームの専用サーバ、俗にいうゲームサーバを指します。
主に家庭用ゲーム機ではプレイヤーの一人がホストとなってマルチゲームを行いますが、PCゲームの大半ではマルチプレイ専用のサーバを別途用意することが多いです。

環境

FreeBSD-7.1 (amd64)
SRCDS は FreeBSD 向けのバイナリはありませんが、Linux エミュレーションで FreeBSD でも動作します。
今回は OS 依存の部分はあまりないので、Linux でも同じ手法でできるはずです。

また、あんまりスペックは要求されないですが、回線は光必須と考えたほうが無難です。
上り回線が細い ADSL や安定しない CATV は諦めてください。

FreeBSD での Linux エミュレーション

方法は割愛。ports から fc10 を使っています。

カーネルモジュールを確認します。

azusa# kldstat | grep linux
 7    1 0xffffffffb073f000 18a2c    linux.ko

linprocfs を mount します。

azusa# cat /etc/fstab | grep linprocfs
linprocfs               /compat/linux/proc linprocfs rw 0 0

SRCDS

FreeBSD ports でインストールします。

azusa# pwd
/usr/ports/games/linux-steam
azusa# make install clean
(ライセンスの確認を行って問題なければ yes)

ただし、SRCDS は ports を使う恩恵があまりないので直接ダウンロードしてもいいかもしれません。

azusa# wget http://steampowered.com/download/hldsupdatetool.bin

SRCDS 実行ユーザの作成

これがポイント。実行ユーザのホームディレクトリに .steam ディレクトリが必要です。
そこそこ大きいファイルがダウンロードされるため、root ではなく専用のユーザを作成したほうが無難です。
(データファイルの保存場所が設定できないっぽい?)

ユーザを作成します。FreeBSD の場合。Linux は useradd コマンドを使ってください。
(pw に -m オプション付けずに面倒くさいことをやっているのはskel経由で生成されるファイルが邪魔だからだけです)

azusa# mkdir /home/steam
azusa# pw useradd steam -d /home/steam
azusa# chown steam:stream /home/steam

steam ユーザにスイッチして初期設定を行います。
steam 側のサーバの機嫌が悪い時には数回行う必要があります。

azusa# su - steam
$ pwd
/usr/home/steam
$ mkdir .stream
$ ./steam
Checking bootstrapper version ...
Getting version 44 of Steam HLDS Update Tool
Downloading. . . . . . . . . . . .Steam Linux Client updated, please retry the command
CAsyncIOManager: 0 threads terminating.  0 reads, 0 writes, 0 deferrals.
CAsyncIOManager: 21 single object sleeps, 0 multi object sleeps
CAsyncIOManager: 0 single object alertable sleeps, 0 multi object alertable sleeps

Team Fortress 2 サーバの構築

サーバに必要なファイルをダウンロードします。
CDNに乗っていないせいか結構時間がかかりますので、風呂にでも入って身を清めておきましょう。

オプション の -game で対象のゲームを指定します。今回は tf です。

$ ./steam -command update -game tf -dir .
Checking bootstrapper version ...
Updating Installation
No installation record found at ./orangebox
No installation record found at ./orangebox
No installation record found at .
No installation record found at .
No installation record found at .
No installation record found at ./orangebox
Checking/Installing 'Team Fortress 2 Content' version 254

0.12%   downloading ./orangebox/tf/bin/server.dll
0.29%   downloading ./orangebox/tf/bin/server.dylib
(このあと延々と)

サーバの cfg をいじります。

% cd orangebox/tf/cfg
% vi server.cfg

Quake 系に近いので特に問題はないはずですが、マップを回すのは別ファイル (mapcycle.txt、maplist.txt)ぽい?
server cvar についてはググるとすぐ出てきます。

当たり前のことですが、テスト時には LAN モードにしてください。(sv_lan 1)

Team Fortress 2 サーバのテスト起動

コンソールから下記のコマンドで起動しみます。

azusa% orangebox/srcds_run -game tf +maxplayers 24 +map ctf_2fort

他のマシンで Team Fortress2 を起動して開発者コンソールから

connect <サーバの IP アドレス>

と実行してサーバに接続できるかを確認してください。

Team Fortress 2 サーバの起動

毎回コマンド打つのも面倒なので、起動用スクリプトを用意します。
FreeBSD なら $LOCALBASE/etc/rc.d あたりに設置します。
(Linux でも動くように依存した処理はいれていません)

azusa# cat tf2server.sh
#!/bin/sh

BASEDIR=/home/steam
USER=steam
CONSOLE_LOG=$BASEDIR/console.log
su "$USER" -c "exec $BASEDIR/orangebox/srcds_run -game tf +maxplayers 24 +map ctf_2fort > $CONSOLE_LOG  2>&1" &
exit

have fun :D

xenの DomainU として FreeBSD を動かす

ようやく作業が完了したので、まとめてみます。

環境と目的

ubuntu あたりを Xen のサーバとして使うほうが楽なのですが、家にいてまで必要以上に Linux は触りたくないので NetBSD で。
ちなみにノート PC には ubuntu 入れて使っています。軽作業ならこれで十分ですしおすし。

Xen サーバ環境

NetBSD 5.1 AMD64 なホスト。メモリ 8GB でXenホストに 1GB割り当て。

NetBSD 5.1 (XEN3_DOM0) #0: Sun Dec 12 18:53:15 JST 2010  root@gomyway:/usr/obj/sys/arch/amd64/compile/XEN3_DOM0

CPU は Athlon X2 BE2350。これが今回の問題。

# grep model\ name /proc/cpuinfo
model name      : AMD Athlon(tm) X2 Dual Core Processor BE-2350
動かしたい DomainU

mpd で PC ルータとして動かすために FreeBSD
今なら Vyatta を入れるほうが楽かもしれませんが、そこはそれ。

準仮想化で動かすためにXen 用のカーネルを作る必要があります。
完全仮想化でも良いかもしれませんが、なんとなく…

構築手順

FreeBSDXenカーネルを作る。

FreeBSD xen の ML を見ると pmap で panic する修正が入っているとのことだったので、CURRENT で作ります。
このあたりの色々な修正は RELENG 8.2 へ MFC されていないので、おとなしく CURRENT です。
(STABLE へは MFC されてます)

CURRENT を作るのは先日の日記を参照。
http://d.hatena.ne.jp/po3/20110515/1305460610

Xen 用のディスクイメージを作る

下記 URL を参照しました。
http://forums.freebsd.org/showthread.php?t=10268

ただし、ディスクイメージを作るときは truncate ではなくて dd コマンドで作っています。

 # dd if=/dev/zero of=disk.img bs=1m count=2k

俗にいう穴あきファイル(sparse) はあんまりよく分からないし、VMWare でも qemu でもパフォーマンスがガタ落ちだったの dd でフルに容量を使って作成しています。
Linux だと virtio を使うとマシになるのですが、やっぱり根本的に遅いし、ディスクも安いのでキニシナイ!

作ったディスクイメージに色々するのは先日の日記を参照。
/etc/fstab を適当に弄って /etc/ttys に xc0 を追加してください。
http://d.hatena.ne.jp/po3/20110514/1305384444

Xen の設定ファイルを作る

これは単純に下記のようなファイルを用意します。

memory =  855
name = "FreeBSD_DOMU"
kernel = '/usr/home/xen/FreeBSD-CURRENT/xen.kernel'
disk = [
'file:/home/xen/FreeBSD-CURRENT/disk.img,hda1,w',
]
extra = 'boot_verbose,boot_single,kern.hz=100,machdep.idle_mwait=0,vfs.root.mountfrom=ufs:/dev/ad0s1a'
vcpus = 1
on_poweroff = 'destroy'
on_reboot = 'restart'
on_crash = 'restart'

machdep.idle_mwait=0 はもういらないかもしれない。

起動してみます。

gomyway# xm create ./xen.cfg -c
Using config file "././xen.cfg".
Started domain FreeBSD_DOMU
3DNow!>
  AMD Features2=0x11f<LAHF,CMP,SVM,ExtAPIC,CR8,Prefetch>
real memory  = 896532480 (855 MB)
avail memory = 866402304 (826 MB)
[XEN] IPI cpu=0 irq=128 vector=RESCHEDULE_VECTOR (0)
[XEN] IPI cpu=0 irq=129 vector=CALL_FUNCTION_VECTOR (1)
[XEN] xen_rtc_probe: probing Hypervisor RTC clock
rtc0: <Xen Hypervisor Clock> on motherboard
[XEN] xen_rtc_attach: attaching Hypervisor RTC clock
xenstore0: <XenStore> on motherboard
xc0: <Xen Console> on motherboard
Timecounters tick every 10.000 msec
xenbusb_front0: <Xen Frontend Devices> on xenstore0
[XEN] hypervisor wallclock nudged; nudging TOD.
xenbusb_back0: <Xen Backend Devices> on xenstore0
xctrl0: <Xen Control Device> on xenstore0
xbd0: 2048MB <Virtual Block Device> at device/vbd/769 on xenbusb_front0
xbd0: attaching as ad0
Timecounter "TSC" frequency 2109592000 Hz quality 800
WARNING: WITNESS option enabled, expect reduced performance.
Trying to mount root from ufs:/dev/ad0s1a []...
rtc0: [XEN] xen_rtc_gettime
rtc0: [XEN] xen_rtc_gettime: wallclock 1306675669 sec; 965899053 nsec
rtc0: [XEN] xen_rtc_gettime: uptime 665 sec; 6026720 nsec
rtc0: [XEN] xen_rtc_gettime: TOD 1306676334 sec; 971925773 nsec
Setting hostuuid: 0f12f2c1-8947-11e0-bda0-6389d0dd6e1d.
Setting hostid: 0x419c4544.
No suitable dump device was found.
Entropy harvesting: interrupts ethernet point_to_point kickstart.
Starting file system checks:
/dev/ad0s1a: FILE SYSTEM CLEAN; SKIPPING CHECKS
/dev/ad0s1a: clean, 835786 free (1474 frags, 104289 blocks, 0.1% fragmentation)
Mounting local file systems:.
/etc/rc: WARNING: $hostname is not set -- see rc.conf(5).
Starting Network: lo0.
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=3<RXCSUM,TXCSUM>
        inet 127.0.0.1 netmask 0xff000000
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
Starting devd.
add net ::ffff:0.0.0.0: gateway ::1
add net ::0.0.0.0: gateway ::1
add net fe80::: gateway ::1
add net ff02::: gateway ::1
Creating and/or trimming log files.
Starting syslogd.
/etc/rc: WARNING: Dump device does not exist.  Savecore not run.
ELF ldconfig path: /lib /usr/lib /usr/lib/compat
a.out ldconfig path: /usr/lib/aout /usr/lib/compat/aout
Clearing /tmp (X related).
Updating motd:.
Starting cron.
Starting background file system checks in 60 seconds.

Sun May 29 13:39:02 UTC 2011

FreeBSD/i386 (Amnesiac) (xc0)

login: 

なんとか起動しました。

TODO

とりあえず動かすところまではできましたが、メモリを 855MBよりも多くすると、また pmap で落ちます。
ルータとして動かすのでメモリはそんなにいらないので、この問題についてはスルーします。

USBメモリからXenServerをインストールしてみる

先日 XenDomU として FreeBSD を入れようと思ったものの、pmap_init で panic で落ちる問題はさっぱり。

そんな訳で現実逃避として XenServer をインストールしてみた。

インストール用の USB メモリを作る

何時買ったのかさっぱり思い出せない適当な 4GB の USB メモリからインストールしてみます。
自宅内のインストールサーバが停止中なので、わざわざ CD-R 焼くのは面倒だし、かなり地球に優しいエコじゃん. Jumping Now ってことで。

やることは

  • FAT32 でフォーマット
  • ISO イメージから必要なファイルをコピー
  • syslinux でブートローダを突っ込む

これだけ。ね、簡単でしょう?

FAT32 で USB メモリをフォーマットする

Windows 環境でやるなら HP USB Disk Storage Format Tool あたりで。
hp.com のどこにあるのかは分からなかったので詳細はググってください。

XenServer の ISO から USB メモリにコピー

Citrix のサイトから XenServer 無償版をダウンロードします。
http://www.citrix.co.jp/products/xenserver/download.html

ISO イメージをマウントできる適当なツール(DAEMON Tools Light)で、ISO の中身をそのまま USB メモリにコピーします。(すべてコピーする必要はないのですが、別に容量が足らないわけではないので)

次に USB メモリにコピーしたディレクトリとファイルをリネームする必要があります。isolinux から syslinux に変更します。
(例として G ドライブに USB メモリがささっている場合)

コマンドプロンプトでの例

 C:\Users\popopo>g:
 G:\>move isolinux\isolinux.cfg isolinux\syslinux.cfg
        1 個のファイルを移動しました。

 G:\>move isolinux syslinux
        1 個のディレクトリを移動しました。
syslinux でブートローダをインストールする

下記 URL から適当にダウンロードします。
http://www.kernel.org/pub/linux/utils/boot/syslinux/

アーカイブを展開後、コマンドプロンプトから

 D:\Download\syslinux-4.04> cp mboot\mboot.c32 g:\syslinux
 D:\Download\syslinux-4.04> cd win64
 D:\Download\syslinux-4.04\win64> syslinux64.exe -am g:

として、必要なファイルを差し替えたのちブートローダを仕込みます。

mboot\mboot.c32 を USB メモリにコピーするのは、ISO 側の syslinux が Ver.3だから(多分)
このファイルを差し替えてやらないと、ローダで延々とエラー吐かれる。
(vesamenu.c32 も差し替えないと駄目なような…)

USB メモリを差してインストール

BIOS で BOOT メニューを出して起動デバイスをしています。ウチの環境では USB-HDD としました。
XenServer のインストールは割愛。

おまけ:1台の XenServer でなんとかする

仮想マシンを構築しようとしたものの、どうも ISO イメージは外部のストレージに置くことが前提、みたいな…
NFS サーバ作るのめんどい、samba サーバ作るのめんどいし、Windowsの共有フォルダがうまく動かない、ぐぬぬ

ググったら下記のサイトが参考になりました。
http://www.noop.tk/index.php?XenServer

/home/iso に各 ISO イメージを格納する場合

# mkdir /home/iso
# xe sr-create name-label="local iso" type=iso device-config:location=/home/iso device-config:legacy_mode=true content-type=iso
fa54baf1-5ed0-e68a-873f-ed5257cce42d 

おまけ:XenCenter 感想

起動が遅い(小学生並の感想)
これはハードウェアスペックに関係なく、サーバ自体の起動がやや遅いです(*BSDに比べて)

似たような製品の VMWare ESX に比べると対応しているデバイスドライバの数は多く、ハードウェア的な制限が低いのが利点、ぐらい。

XenServer の HCL。
http://hcl.xensource.com/

FreeBSD-CURRENT の環境を作る

Xen の DomainU で FreeBSD を動かしたいけれど、うまく動かないでの CURRENT の Xen kernel を作ってみることに。

FreeBSD CURRENT の作り方はハンドブックを眺めると書いてありますが、個人的にはあまりやらない作業なので、なにをやったかを記録しておきます。

やり方としては手抜きですが、仕事でやるときは丁寧にやってます、はい(明後日の方向を見ながら)

csup で使う supfile を作ります。これは cvsup のものを転用します。tag は . を指定します。

gomyway# cp /usr/share/examples/cvsup/standard-supfile /home/work
gomyway#  egrep -v '^#|^$' /home/work/standard-supfile
*default host=cvsup3.jp.freebsd.org
*default base=/var/db
*default prefix=/usr
*default release=cvs tag=.
*default delete use-rel-suffix
src-all
gomyway# csup /home/work/standard-supfile

csup でソースを拾ってきてビルドします。kernel をインストールして新しい kernel で再起動します。

gomtway# cd /usr/src
gomywat# make -j64 buildworld && make buildkernel && make installkernel
gomyway# reboot

本来なら single mode で作業をすべきなのですが、手が届く端末ですし普通に。こういうことをやっていいのか、それとも駄目なのかは自分で判断してください。今回は素の試験環境なのでそのあたりは気にせずに。

installworld します。ややこしいことが嫌いなのでまたリブートします。

gomyway# cd /usr/src
gomyway# make installworld
gomyway# reboot

mergemaster で /etc を可愛がります。本来なら installworld の前にすべきなのですが、忘れてた。

また、面倒な mergemaster の代替ツールがあったはずなのですが思い出せませんでした。

gomyway# mergemaster

(cvd id を無視するの忘れてた)

本来の目的である CURRENT での Xen kernel を作ります。

gomyway# cd /usr/src
gomyway# make buildkernel KERNCONF=XEN

前回手を抜いたビルドした kernel がどこに生成されるのかは…そりゃ /usr/obj 以下ですよね、アタシってほんと馬鹿。

gomyway# find /usr/obj -name kernel
/usr/obj/usr/src/sys/XEN/kernel
/usr/obj/usr/src/sys/GENERIC/kernel

installkernel 時になにかやっているわけではなさそうなので、ここから kernel をコピーします。

gomyway# sha256 /boot/kernel/kernel
SHA256 (/boot/kernel/kernel) = caeef8f9882d7538c5a8f38e2488822b48f43f6fafdece80c8efade6ca548956
gomyway# sha256 /usr/obj/usr/src/sys/GENERIC/kernel
SHA256 (/usr/obj/usr/src/sys/GENERIC/kernel) = caeef8f9882d7538c5a8f38e2488822b48f43f6fafdece80c8efade6ca548956m

こんな感じ。

本来の目的である DomainU での FreeBSD は状況変わらず。

# xm create -c ./xen.cfg
Using config file "././xen.cfg".
Started domain FreeBSD8.22_i386_DOMU
WARNING: loader(8) metadata is missing!
GDB: no debug ports present
KDB: debugger backends: ddb
KDB: current backend: ddb
Copyright (c) 1992-2011 The FreeBSD Project.
Copyright (c) 1979, 1980, 1983, 1986, 1988, 1989, 1991, 1992, 1993, 1994
        The Regents of the University of California. All rights reserved.
FreeBSD is a registered trademark of The FreeBSD Foundation.
FreeBSD 9.0-CURRENT #1: Sun May 15 18:55:50 JST 2011
    admin@gomyway.continue:/usr/obj/usr/src/sys/XEN i386
WARNING: WITNESS option enabled, expect reduced performance.
panic: pmap_init: page table page is out of range
cpuid = 0
KDB: enter: panic
[ thread pid 0 tid 0 ]
Stopped at      0xc0119c4a:     movl    $0,0xc0423af4
db>


どうするかね、これ。

FreeBSDのXen Kernelを作る

放置するのもなんですし、日々の調べ物を書くチラシの裏として活用しようかと。
しかしながら、内容が小出しになる、もしくは途中で無駄だと判断して中断することもあるので、答えが知りたくてググッてきた人には悪いかもしれません。

んじゃ、本題。

NetBSD 5.1 (AMD64)で動いている Xen 環境に DomainU として FreeBSD を動かしたいので、準仮想化用に使う Kernel を作ってみました。

いつもの方法で kernel を作ります。

gomyway# uname -rm
8.2-RELEASE i386
gomyway# cd /usr/src/
gomyway# make buildkernel KERNCONF=XEN

ここでちょいと迷ったのが、どこにビルドされた kernel があるのか分からない。面倒になったので、適当なパーティションを作ってそこにインストールしてから取り出すことにしてました。

適当にイメージファイルを作って mdconfig でメモリディスクをアタッチします。

gomyway# dd if=/dev/zero of=/home/freebsd8.2.img bs=1m count=1024
gomyway# mdconfig -a -t vnode -u 0 -f /home/freebsd8.2.img

ファイルシステムを作って bsdlabel で fstype を 4.2BSD に変更します。

gomyway# bsdlabel -w /dev/md0 a
gomyway# bsdlabel -e /dev/md0
gomyway# newfs /dev/md0a

mount します。

gomyway# mount /dev/md0a /mnt

メモリディスクにカーネルをインストールします。

gomyway# cd /usr/src
gomyway# make installkernel DESTDIR=/mnt  KERNCONF=XEN

これで /mnt/boot/kernel/kernel に Xen kernel があります。

これを Xen ホストにコピーして DomainU として起動してみますが… OS起動時にpmap_init で落ちる。

# xm create -c freebsd.conf
(snip)
panic: pmap_init: page table page is out of range

ワケがわからないよ。

ググッてみたら、AMD特有の問題のようで 2011年1月ぐらいにFIX入っているみたいなので、後日CURRENT で Xen Kernel を作ってもう一度トライしてみます。