まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2015/02

自分が作って運用しているサービス "Tweets Mapper" を紹介します:
http://tweetsmapper.juge.me/

上記ページにアクセスしていただくと、利用者の現在地を中心としたエリアが地図に表示されます。
そして、その地図画面内において、ツイッターで「位置情報を ON にして」ツイートされたもののうち、
 ・過去3時間以内のものを
 ・近いものから20個
位置情報と併せて表示する、というサービスです。

「地図画面内でのツイート」を検索して表示するので、地図の表示位置や拡大率を変えて、表示エリアが変わるとツイート一覧もその内容に併せてかわります。

この画面例はつい先程、ハワイのホノルル周辺が含まれるよう地図を調整して撮ったものです:
2015022701

また画面上部には現在表示している地図の位置と拡大率を初めから表示するための URL です。例えば上記ページの URL はこちらで、この URL にアクセスすると、地図を調整しなくてもはじめからホノルル周辺で最近ツイートされた内容が表示されます。実際にアクセスすると、ホノルルで日本語ツイートがいかに多いかわかると思います:
http://tweetsmapper.juge.me/?lat=21.283109926822885&lng=202.1684304199951&zoom=15


PC のブラウザでアクセスすると、このように画面が左右2つに別れて表示されますが、スマホの場合は地図とツイートとが別タブで表示されます:
2015022702


コンサートやイベントなど、人が多く集まってツイートが多くされるとわかっている時に、この TweetsMapper が威力を発揮します。後でツイートをまとめたい時に、全員が共通のハッシュタグを付けてくれるとまとめやすいのですが、全員がそうしてくれるとは限りません。その点、この TweetsMapper では「位置情報が ON になっている」ことが条件にはなりますが、ハッシュタグがなくてもそのイベント周辺にいる人のツイートを取得できるので、集計が楽になります。

また、この手の検索はリアルタイム性を追求するのが結構難しいのですが、ちょっと工夫した仕組みを導入することでうまく実現できていると自負しています。実際に見ていると、ほぼリアルタイムに(本当に直前にツイートされたものも)画面に表示できているはずです。 このサービスを実現する上ではサーバー側の通信速度の確保が必要なんですが、IDCF クラウドで非常に高いコストパフォーマンスを提供していただくことで実現できています。



ちなみに、現在のこのサービスでは日本全国に加えて、ハワイ、LA、SF、ラスベガス、NY、ボストン、フロリダ周辺がサービス提供エリアとなっています。理論上は他のエリアを含めることもできますが、予算とその他の関係上(笑)いまはこのエリアに絞って提供していることをご理解ください。

でも、もし他エリアの要望があれば教えてください。一時的な対応も含めてですが、検討します。






 

BlackBerry Passport を購入して、低速定額 MVNO (通話なし)で使っています。

現実問題として自分はデータ通信中心に使っていて、通話はそんなにしないので、通話契約がないこと自体で困ることはありません。
ただ最近は IP 電話サービスが充実してきていて、通話契約がなくても 050 ではじまる電話番号を取得して発着信に使えるようになっています。特にフュージョンコミュニケーションズ社の SMARTalk サービスは初期費用と月額基本料が0円、また着信通話には料金がかからないので、着信専用電話と割りきって使うぶんには無料で環境が作れてかなり便利です(ただし発信時の料金は少し高め)。自分はこれまで Android 機に SMARTalk アプリを入れて使っていました。フュージョンコミュニケーションズさま、お世話になってます!:

↓iOS / Android 用の専用アプリが提供されているようです。
2015022500


SMARTalk 自体は一般的な IP 電話なので、正しい設定をすれば専用アプリを使う必要はありません。BlackBerry のアプリマーケットである BlackBerry World には SMARTalk アプリはありませんが、一般的な IP 電話アプリをダウンロードして設定すれば使えるはずです。 ただ1つ問題がありました。BlackBerry ネイティブの IP 電話アプリケーションは全て有料のものしかなく、「気軽に試してみる」というテンションだと二の足を踏んでしまいます。特に自分の場合は低速 MVNO を使っているという背景もあり、どこまで実用的なのか実験的な意味合いも強いのでした。「お金を払って実験したいわけじゃないんだよなあ」という感じ:

↓有料アプリばかり・・・
2015022501



そこで今回試したのは「BlackBerry 上で Android ネイティブな無料の IP 電話アプリが使えるか?」という実験(の実験?)です。BlackBerry は比較的新しい機種(Q5 以降)の最新アップデートでは Android ネイティブアプリがインストールできるようになっているので、これを使って無料の IP 電話環境を作ってしまおう、というアイデアです。 結論としては使えているように見えるので、以下にその手順を紹介します。

まずは SMARTalk のアカウントが必要です。まだ持っていない人は SMARTalk のアカウントを作成しましょう:
http://ip-phone-smart.jp/notes/

IP 電話の設定をする上で必要になるのは以下の情報です:
- SIP ドメイン(SMARTalk の場合はおそらく smart.0038.net で固定)
- SIP アカウント(待ち受け電話番号 050-XXXX-XXXX の下8桁部分の数字)
- SIP パスワード


次に BlackBerry Passport にプリインストールされている Amazon Appstore アプリを起動して、"AGEPhone" を検索します。見つかったらインストールします(AGEPhone 以外の IP 電話アプリでもできると思いますが未確認):
2015022502


インストールできると BlackBerry Passport のホーム画面に AGEPhone のアイコンが追加されます。これをタップして起動し、SMARTalk の設定を行います:
2015022503


初回起動時にはプロファイル情報が存在していないのでこのような画面になると思います。「ここをタップして下さい。」と書かれた箇所をタップしてプロファイル設定画面に移動します:
IMG_20150225_115949


プロファイル設定画面では最初にテンプレートが選択できます。ただ SMARTalk 用のテンプレートは AGEPhone には用意されていないようなので「標準の SIP アカウント」を選択します:
IMG_20150225_115956


最初にこのプロファイルに名前をつけます。適当に(この例では "smartalk")名前をつけてください:
IMG_20150225_120021


次の画面でアカウント接続情報を入力します。ドメインに SIP ドメイン名(おそらく "smart.0038.net")、ユーザIDと認証IDには SIP アカウント(着信電話番号 050-XXXX-XXXX の下8桁数字)、そしてパスワード欄に SIP パスワードを入力します。最後に「完了」で保存します:
IMG_20150225_120336


すると画面上部が「サーバに登録中」という画面になって・・・
IMG_20150225_120403


しばらくすると「ダイアルできます」という画面に変わります。これで SMARTalk に接続できました。この段階で与えられた 050 番号での着信も確認できます:
IMG_20150225_120408


「キーパッド」からダイアル画面に移動して、ここから発信することも可能です:
IMG_20150225_120425


ちょっと不安でしたが、Android 向けの IP 電話アプリも BlackBerry で動かすことができそうです。 ちなみに気になる音質ですが、WiFi や LTE であれば全然問題ないと思います。低速回線(200Kpbsベストエフォート)でも、うん、まあ、なんとか・・・という感じ。Radiko を使った時にも感じましたが、音声データだけならこの程度の回線速度でも意外といけます。








 

自分は自宅に CentOS を中心に構築した簡易ローカルネットワークがあります。自宅には固定の光回線もあるので、ブロードバンドルーターのポートフォワーディング機能を使って自宅内の HTTP サーバーを公開したり、SSH サーバーや DB サーバーににインターネットからログインできるようにしていました。
2015022401



もちろんこれはこれで便利ではあったのですが、自宅内に KVM の仮想サーバーを構築したあたりから少しずつ限界を感じることがでてきました。仮想サーバーができたことで簡単&気楽にマシンを増やして使うことができるようになるのはいいのですが、動的に増やしたマシンに接続するための設定として、2つ以上の異なる HTTP サーバーに 80 番ポートで接続したい、とか、ポートフォワーディングだけでは限界を感じることが多くなってきたのでした。まあ自宅ネットワーク環境もこんな悩みを抱えるレベルになれば立派かな、という感じ:
2015022402




この状況を打破するべく、 自宅ネットワークに VPN(Virtual Private Network) 環境を構築することにしました。結果的にですが、PC と自宅ネットワークとの間の通信をすべて暗号化することにもなるので、セキュリティ的にも向上することになります。この環境をオープンソースの OpenVPN を使ってすべて無料で構築したのですが、その手順をまとめました。


まず目標として、最終的には以下の様なネットワーク構成を作ることにしました:
・自宅ネットワークは 192.168.0.0/24 で、ルータ/DHCP/DNS 含めて今まで使ってきた自宅ネットワーク環境を変えずにそのまま使う。 
・自宅ネットワークのブロードバンドルータは 192.168.0.1、このブロードバンドルータの公開サーバー名は www.myserver.com とする。
・OpenVPN を導入する VPN サーバーは 192.168.0.4(CentOS)
・VPN で接続するクライアントは Windows7 を想定
・VPN で接続するクライアントのネットワークは 10.8.0.0/24(OpenVPN のデフォルト)
・VPN で接続したクライアントから自宅ネットワークへ、自宅ネットワーク内のマシンから VPN で接続したクライアントへの双方向の通信を可能にする
2015022403


上記の最後の文を補足します。この環境ができて VPN 接続ができた後に、VPN のクライアント機(Windows7)からは 192.168.0.XX というそのままのアドレスで自宅ネットワークの各サーバーに接続ができるようになります。加えて自宅ネットワーク内の各サーバー機からも 10.8.0.X というアドレスを指定することで Windows7 機にアクセスできるようになる、この双方向でのアクセスが可能になるような環境とする、ということを目標にします。


では以下にその構築手順を記載します。まず自宅ネットワーク内の1台のマシンに OpenVPN サーバーをインストールします。今回はこのマシンの IP アドレスが 192.168.0.4 で、CentOS が導入されているものと仮定します。また 2015/02/24 時点では OpenVPN の最新バージョンが 2.3.6 だったので、このバージョンをインストールしています。実際にはこちらを参照して、最新バージョンを確認し、最新バージョンのモジュールをダウンロードするようにしてください。

このマシンにログインして root 権限でシェルを開き、以下のコマンドを順次実行します:
# cd /usr/local/src
# yum -y install openssl-devel lzo-devel pam-devel

(最新の OpenVPN をダウンロードしてビルド)
# wget http://swupdate.openvpn.org/community/releases/openvpn-2.3.6.tar.gz # rpmbuild -tb --clean openvpn-2.3.6.tar.gz # yum -y localinstall ~/rpmbuild/RPMS/x86_64/openvpn-2.3.6-1.x86_64.rpm # rm -f ~/rpmbuild/RPMS/x86_64/openvpn-* # rm -f openvpn-2.3.6.tar.gz
(easyrsa をダウンロードしてビルド) # wget https://github.com/OpenVPN/easy-rsa/archive/master.zip # unzip master.zip # cp -r easy-rsa-master/easyrsa3/ /etc/openvpn/ # rm -rf easy-rsa-master/ # rm -f master.zip

これで OpenVPN の導入はできました。次にこの導入モジュールを使って各種鍵・証明書ファイルを作ります:
(初期化)
# cd /etc/openvpn/easyrsa3/
# ./easyrsa init-pki

(CA証明書と秘密鍵作成)
# ./easyrsa build-ca
 (パスフレーズの入力を促されるので2度同じ内容を入力)
 (Common Name(サイト名)を聞かれたらサーバー名(www.myserver.com)を入力)
# cp pki/ca.crt /etc/openvpn/

(サーバー証明書と秘密鍵作成)
# ./easyrsa build-server-full server nopass
 (パスフレーズを聞かれるので、先ほど入力したパスフレーズを入力)
# cp pki/issued/server.crt /etc/openvpn/
# cp pki/private/server.key /etc/openvpn/

(DHパラメータ作成)
# ./easyrsa gen-dh
# cp pki/dh.pem /etc/openvpn/

(ダミーのクライアント証明書作成)
# ./easyrsa build-client-full dmy nopass
 (パスフレーズを聞かれるので、先ほど入力したパスフレーズを入力)

(ダミーのクライアント証明書廃止)
# ./easyrsa revoke dmy
 ("Continue with revocation" と表示されたら yes と入力)
 (パスフレーズを聞かれるので、先ほど入力したパスフレーズを入力)

(証明書廃止リスト作成)
# ./easyrsa gen-crl
 (パスフレーズを聞かれるので、先ほど入力したパスフレーズを入力)
# cp pki/crl.pem /etc/openvpn/
# chmod o+r /etc/openvpn/crl.pem

(TLS認証鍵作成)
# openvpn --genkey --secret /etc/openvpn/ta.key
# cp /usr/share/doc/openvpn-2.3.6/sample/sample-config-files/server.conf /etc/openvpn/

上記で最後にコピーした OpenVPN の設定ファイルを編集します:
# vi /etc/openvpn/server.conf
    :
  (変更が必要な該当箇所のみ記述、ここにある内容以外はそのままで)
    :
dev tun

dh dh.pem

server 10.8.0.0 255.255.255.0

;push "route 192.168.10.0 255.255.255.0"
;push "route 192.168.20.0 255.255.255.0"
push "route 192.168.0.0 255.255.255.0"

tls-auth ta.key 0 # This file is secret

user nobody
group nobody

log-append  /var/log/openvpn.log

# 以下の2行を最終行に追加
management localhost 7505
crl-verify crl.pem

更に easyrsa3 ディレクトリでクライアント証明書および秘密鍵ファイルを生成しておきます。なお、以下の例では client1 という名前のクライアント証明書・秘密鍵ファイルを生成していますが、このクライアント名は一意である必要があります。複数クライアント向けに複数の証明書や秘密鍵ファイルを生成する場合は、既に作成したクライアント名と重ならないように注意してください:
# cd /etc/openvpn/easyrsa3
# ./easyrsa build-client-full client1
 (クライアント用パスフレーズの入力を促されるので2度同じ内容を入力)
 (サーバー用パスフレーズを聞かれるので、作成時に入力したパスフレーズを入力)

起動スクリプトとシャットダウンスクリプトを新規に作成します:
# vi /etc/openvpn/openvpn-startup
#!/bin/bash

/etc/openvpn/openvpn-shutdown
iptables -I OUTPUT -o tun+ -j ACCEPT
iptables -I FORWARD -o tun+ -j ACCEPT

iptables -I INPUT -i tun+ -j ACCEPT

iptables -I FORWARD -i tun+ -d 192.168.1.0/24 -j ACCEPT


# chmod +x /etc/openvpn/openvpn-startup
# vi /etc/openvpn/openvpn-shutdown
#!/bin/bash

delete() {
  rule_number=`iptables -L $target --line-numbers -n -v|grep tun.|awk '{print $1}'|sort -r`
  for num in $rule_number
  do
    iptables -D $target $num
  done
}

target='INPUT'
delete

target='FORWARD'
delete

target='OUTPUT'
delete

# chmod +x /etc/openvpn/openvpn-shutdown

ログローテーションの設定ファイルを新規に作成します:
# vi /etc/logrotate.d/openvpn
/var/log/openvpn.log {
  missingok
  notifempty
  sharedscripts
  postrotate
    systemctl restart openvpn 2>&1 > /dev/null || true
  endscript
}

ここまでの準備ができていれば OpenVPN サーバーを起動し、自動起動設定も行います:
# vi /etc/init.d/openvpn

 (この行のコメントを外す)
  echo 1 > /proc/sys/net/ipv4/ip_forward


# /etc/init.d/openvpn start
# chkconfig openvpn on

次にルータ(この例では 192.168.0.1)上で UDP/1194 へのアクセスを VPN サーバー(この例では 192.168.0.4)にフォワーディングするよう、ポートフォワード(と必要であればファイアウォール)の設定を行います。この具体的な手順はお使いのルータごとに異なるため、ルータの説明書などを参照しておこなってください:
2015022404


更にルータ上のルーティングを変更し、このネットワーク内での 10.8.0.0/24 宛のデータは VPN サーバー(この例では 192.168.0.4)をゲートウェイとするようにルーティングエントリを追加します。これで VPN ネットワークに接続したマシンへネットワーク側から接続することもできるようになります。この具体的な手順もお使いのルータごとに異なるため、ルータの説明書などを参照しておこなってください:
2015022405


これで OpenVPN サーバーおよびルータ型の設定が完了しました。 続けて VPN クライアント(PC側)の設定を行います。

この VPN ネットワークに接続したい PC で OpenVPN のダウンロードページを開き、環境にあった Installer をダウンロード・ダブルクリックして導入します:
2015022406


導入先フォルダの中にクライアント設定ファイルのサンプル(C:\Program Files\OpenVPN\sample-config\client.ovpn)があるので、このファイルを C:\Program Files\OpenVPN\config\client.ovpn としてコピーし、以下の内容に書き換えます(変更箇所のみ記載しています):
remote www.myserver.com 1194 # VPN サーバー名

ca ca.crt # CA証明書のファイル名
cert client1.crt # クライアント証明書のファイル名
key client1.key # クライアント秘密鍵のファイル名

tls-auth ta.key 1 # 行頭にコメントが付いているので外して TLS 認証を有効化

VPN クライアントから VPN サーバーのマシンに SFTP などで接続し、CA 証明書(/etc/openvpn/ca.crt)/クライアント証明書(/etc/openvpn/easyrsa3/pki/issued/client1.crt)/クライアント秘密鍵(/etc/openvpn/easyrsa3/pki/private/client1.key)/TLS認証鍵(/etc/openvpn/ta.key)の各ファイルを転送し、すべて C:\Program Files\OpenVPN\config フォルダ内に保存します。


最後に、この VPN クライアントは Windows7 上では管理者権限で実行する必要があるため、その設定を行います。

スタート → すべてのプログラム → OpenVPN → OpenVPN GUI を右クリックし、「プロパティ」を選択します:
2015022407


"OpenVPN GUI のプロパティ" ダイアログが表示されたら「互換性」タブを選び、「管理者としてこのプログラムを実行する」にチェックを入れます。最後に OK をクリックしてダイアログを閉じます:
2015022408



これで準備はすべて整いました。最後に実際に VPN 接続して動作確認してみます。

VPN クライアントを導入した PC が自宅以外のインターネットに接続している状態で、先ほどの手順で OpenVPN GUI を選択して起動します:


OpenVPN クライアントがタスクバーに格納された状態で起動します。この時点では接続できていないので灰色で表示されているはずです。このアイコンを右クリック → 接続を選択します。
2015022409


OpenVPN のコンソールが表示され、パスワードを聞かれます。ここではクライアントの証明書を作成する時に指定したパスフレーズを入力して OK ボタンをクリックします:
2015022410


正しく接続できるとアイコンが緑色になります。同時に新たに割り当てられた IP アドレス(この例では 10.8.0.6)が吹き出し表示されます:
2015022411


この状態で自宅ネットワーク内のサーバーの IP アドレス(192.168.0.XX)を指定すれば接続できるはずです:
2015022412

逆に自宅ネットワーク内のサーバーから割り当てられた IP アドレスに対しても接続できているはずです:
2015022413


繋がりました! これで外から自宅ネットワークに対してセキュアに、しかもポートフォワードの制約を受けずにアクセスできるようになりました。

VPN 接続を切断するにはタスクバーのアイコンを右クリック→切断 を選択します:
2015022414



今回紹介した方法は Windows クライアントを前提としていましたが、証明書や秘密鍵の作り方を少し変えると iPhone などでも利用できるようになります。 詳しくは以下の参考ページを参照ください:


(参考)
http://centossrv.com/openvpn.shtml


 

CentOS / RHEL(RedHat Enterprise Linux) に Ruby 2.x をインストールする手順を紹介します。

Ruby のバージョンにこだわらないのであれば、コマンドラインから
# yum install ruby
と入力するだけでインストールできます。ただ 2015/Feb/18 現在、この方法でインストールできる Ruby のバージョンは 1.8.7 でした。このバージョンで特に支障がなければこの方法が一番簡単だと思っています。


以下、どうしても Ruby 2.x を使いたい、という人のための導入方法の紹介です。CentOS で Ruby 2.x を導入する方法はいくつかあって、ソースコードからビルドするという一般的な手順もあるのですが、ここでは rbenv を使った方法を紹介します。

rbenv は複数バージョンの Ruby を同一コンピュータ上で管理する GitHub 提供のツールです。2.x に限らず 1.x も含めた複数バージョンの Ruby を同じマシンにインストールし、実際に利用するバージョンを管理しながら使えるようにできます。このツールを使って Ruby の最新版をインストールしてみます。


まず、rbenv は GitHub から提供されていることもあり、git の入手が必須です。まだ導入できていない場合はこのコマンドで git を導入してください:
# yum install git

そして git を使って GitHub から rbenv をチェックアウトします。今回は /opt/ 以下に Ruby をインストールするという前提にします:
# cd /opt
# git clone git://github.com/sstephenson/rbenv.git

続けて ruby-build プラグインを rbenv の plugins フォルダにインストールします:
# mkdir /opt/rbenv/plugins
# cd /opt/rbenv/plugins
# git clone git://github.com/sstephenson/ruby-build.git

最後に /etc/profile に以下の3行を追加して、rbenv を初期化&実行するための環境設定を行います:
export RBENV_ROOT="/opt/rbenv"
export PATH="${RBENV_ROOT}/bin:${PATH}"
eval "$(rbenv init -)"

ここまでの作業で rbenv の導入が完了しました。再ログインするとか、シェルを再起動するなりして最後の環境設定までが反映された状態にしてください。

更に、この後の作業で必要になるモジュールを(エラーが出る前に)yum でインストールしておきます:
# yum install gcc make openssl-devel libffi-devel

ここまでの準備ができていれば Ruby のインストール作業に移れます。まずは rbenv でインストールできる Ruby のバージョン一覧を確認してみます:
# rbenv install -l
Available versions:
  1.8.6-p383
  1.8.6-p420
  1.8.7-p249
  1.8.7-p302
  1.8.7-p334
  1.8.7-p352
  1.8.7-p357
  1.8.7-p358
    :
    :
  2.1.5
  2.2.0-dev
  2.2.0-preview1
  2.2.0-preview2
  2.2.0-rc1
  2.2.0
  2.3.0-dev
  jruby-1.5.6
    :
    :
  ree-1.8.7-2012.02
  topaz-dev

JRuby とかもリストされてますが、今回は普通(?)の Ruby だけで考えます。バージョン番号に rc とか dev とか preview とか付いているのは開発中のものだったりします。この一覧を見る限り安定版では 2.2.0 が最新のようなので、これをインストールしてみましょう(結構時間かかります):
# rbenv install 2.2.0
Downloading ruby-2.2.0.tar.gz...
    :
    :


そして、この Ruby 2.2.0 をシステムで使うデフォルトバージョンとして設定した上で動作確認します:
# rbenv global 2.2.0
# ruby -v
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-linux]

Ruby 2.2.0 が導入できました! 


この後で別のバージョンが必要になった場合は同様に
# rbenv install X.X.X

でインストールし、
# rbenv global X.X.X

でデフォルトバージョンを変更できます。







 

KVM(Kernel-based Virtual Machine) は CentOS/RHEL(RedHat Enterprise Linux) 環境に標準装備の仮想ハイパーバイザーです。わかりやすく言えば、VMWare Server のような機能がカーネルにはじめから用意されている(はじめから有効にはなっていない)ので、CentOS/RHEL でも、それ以外の Linux ディストリビューションでも単体で仮想環境を構築することができます。 僕も自宅の個人環境で使っています。KVM 環境の構築手順はこちらのエントリを参照ください:
CentOS に KVM 環境を構築する


KVM には GUI/CUI 両方の管理ツール/コマンドが用意されていて、仮想イメージのクローン(複製)なども行うことができます。ただこの複製ツールが手軽に使えるかというと、実用上ちょっとした問題がありました。
2015021801


その問題点は、この「複製」というのは「仮想イメージをまるごと複製」するのですが、ホスト名と MAC アドレスまでまるごと複製してしまう、という点でした。つまりあるサーバーインスタンスのイメージを複製すると、同じホスト名で、同じ MAC アドレスのネットワークインターフェースを持った異なるサーバーが用意される、ということです。 またネットワーク設定も複製されるため、例えば固定 IP アドレスが設定されている環境であれば同じ IP アドレスが割り振られることになりますし、DHCP 設定だとすると、同じ MAC アドレスのサーバーが別に存在していることになり、DHCP サーバーからは区別ができません(結果、正しい IP アドレスが取得できず、ネットワークが使えない状態で起動してしまいます)。


要は、複製コマンドそのものに問題があるわけではない(むしろ、複製という意味では正しい)のですが、イメージを複製した後にホスト名と MAC アドレスを変更したい(そこまで含めて自動化したい)のです。ただホスト名は AAA から BBB に変更すればよい、という正解が決まっているのでいいとして、MAC アドレスは変更したいといっても「では変更後の値を何にするのか?、一意に生成できるのか?」という問題もあったりして単純ではありません。またこれらの処理を自動化するとなると、起動していない仮想サーバーイメージ内のファイルシステムを外から操作することになるので、これも特殊なコマンドを使うことになります。その辺りを含めてシェルスクリプト化するまでを紹介します。


というわけで、目標として今回は clone-guest.sh というシェルスクリプトを作って、
# clone-guest.sh AAA BBB
というコマンドで、AAA という仮想マシンイメージを BBB という仮想マシンに複製できるようにします。複製そのものは /usr/bin/virt-clone コマンドを使えばできるのですが、その際に BBB は AAA とは異なる MAC アドレスを持っているように変更します。また複製後にこの BBB を自動起動するようにしてみます。また今回作成するシェルスクリプトでは複製の対象を CentOS/RHEL とします(これらのシステムを前提として MAC アドレス変更の処理を追加するためです)。


まず最初に「そもそもホスト名と MAC アドレスの変更ってどうやるの?」という疑問が生じます。ホスト名については CentOS/RHEL では /etc/sysconfig/network 内に HOSTNAME=**** として記載されているので、ここを書き換えるだけで済みます。具体的には例えば複製元の /etc/sysconfig/network に HOSTNAME=AAA.domain.com のような記載が含まれていたら、複製先では HOSTNAME=BBB.domain.com と書き換えればいい、ということになります。

一方、MAC アドレスはちと複雑です。まず MAC アドレスを書き換える必要のあるファイルが、
 - /etc/sysconfig/network-scripts/ifcfg-eth0
 - /etc/udev/rules.d/70-persistent-net.rules
の2つあります。更に書き換え後の MAC アドレスは現在とは異なる一意の値にする必要があるため、どのように生成するか、という問題も含まれているのでした。ここは詳しくは後述しますが /usr/bin/uuidgen コマンドを使って動的に取得するようにします。
 

次に、起動していない仮想イメージファイル内のファイルシステムへどのようにアクセスするか? ですが、こちらは KVM 標準の以下のコマンドを活用して実現します:
- /usr/bin/virt-cat     外部から仮想イメージ内の特定ファイルに対して cat を実行する
- /usr/bin/virt-copy-in 外部から仮想イメージ内の特定ファイルにコピーする

これらのツールと、KVM の一般的な仮想管理コマンドである virsh(/usr/bin/virsh) を組み合わせて、clone-guest.sh を以下のような内容で作りました。青字は僕のコメントです:
#!/bin/sh

# 作業ディレクトリ
work_dir=/tmp/clone-guest

# この作業で使う各種コマンド
cmd_awk=/bin/awk
cmd_cat=/bin/cat
cmd_cut=/bin/cut
cmd_grep=/bin/grep
cmd_mkdir=/bin/mkdir
cmd_sed=/bin/sed
cmd_uuidgen=/usr/bin/uuidgen
cmd_arp=/sbin/arp
cmd_virsh=/usr/bin/virsh
cmd_virt_cat=/usr/bin/virt-cat
cmd_virt_clone=/usr/bin/virt-clone
cmd_virt_copy_in=/usr/bin/virt-copy-in

source_bridge=br0 # KVM のブリッジインターフェース名(デフォルトだと virbr0)
original_domain_name=$1 # 複製元(AAA)
clone_domain_name=$2 # 複製先(BBB)

# 仮想イメージのあるディレクトリ
domain_image_dir=/var/lib/libvirt/images
clone_domain_image_path=$domain_image_dir/$clone_domain_name.img # 複製先仮想イメージファイル


# パラメータを2つ(複製元と複製先)指定して実行していることを確認
if [ "$clone_domain_name" = "" ] ; then
echo "Usage: clone-guest.sh <domain_name> <clone_name>"
exit 0
fi

# 作業ディレクトリが存在していなかったら作成
[ ! -e $work_dir ] && $cmd_mkdir -p $work_dir


# virt-clone コマンドで普通に複製
$cmd_virt_clone \
  --original $original_domain_name \
  --name $clone_domain_name \
  --file $clone_domain_image_path 

# 複製前の仮想イメージ内の /etc/sysconfig/network を取り出して作業ディレクトリに保存
$cmd_virt_cat -d $original_domain_name \
  /etc/sysconfig/network \
  > $work_dir/network.org

# 複製前の仮想イメージ内の /etc/sysconfig/network-scripts/ifcfg-eth0 を取り出して作業ディレクトリに保存
$cmd_virt_cat -d $original_domain_name \
  /etc/sysconfig/network-scripts/ifcfg-eth0 \
  > $work_dir/ifcfg-eth0.org

# 複製前後の仮想イメージの MAC アドレスを取り出す
original_mac_addr=$( \
$cmd_virsh domiflist $original_domain_name \
| $cmd_grep $source_bridge \
| $cmd_awk '{print $5}' \
)
clone_mac_addr=$( \
$cmd_virsh domiflist $clone_domain_name \
| $cmd_grep $source_bridge \
| $cmd_awk '{print $5}' \
)

# 複製前後の仮想イメージの UUID を取り出す
original_nic_uuid=$( \
$cmd_cat $work_dir/ifcfg-eth0.org \
| $cmd_grep -i uuid \
| $cmd_cut -d'=' -f2 \
| $cmd_sed 's/"//g' \
)
clone_nic_uuid=$($cmd_uuidgen)

# 複製後の仮想イメージの ifcfg-eth0 を書き換え
$cmd_cat $work_dir/ifcfg-eth0.org \
| $cmd_sed -e "s/$original_mac_addr/$clone_mac_addr/i" \
-e "s/$original_nic_uuid/$clone_nic_uuid/i" \
> $work_dir/ifcfg-eth0

# 複製後の仮想イメージに書き換え後の ifcfg-eth0 を書き戻す
$cmd_virt_copy_in -d $clone_domain_name \
  $work_dir/ifcfg-eth0 \
  /etc/sysconfig/network-scripts/

# 複製前の仮想イメージ内の /etc/udev/rules.d/70-persistent-net.rules を取り出して作業ディレクトリに保存
$cmd_virt_cat -d $original_domain_name \
  /etc/udev/rules.d/70-persistent-net.rules \
  > $work_dir/70-persistent-net.rules.org

# 複製後の仮想イメージの 70-persistent-net.rules を書き換え
$cmd_cat $work_dir/70-persistent-net.rules.org \
  | $cmd_sed "s/$original_mac_addr/$clone_mac_addr/i" \
  > $work_dir/70-persistent-net.rules

# 複製後の仮想イメージに書き換え後の 70-persistent-net.rules を書き戻す
$cmd_virt_copy_in -d $clone_domain_name \
  $work_dir/70-persistent-net.rules \
  /etc/udev/rules.d/

# 複製後の仮想イメージの network を書き換え
$cmd_cat $work_dir/network.org \
| $cmd_sed -e "s/$original_domain_name/$clone_domain_name/i" \
> $work_dir/network

# 複製後の仮想イメージに書き換え後の network を書き戻す
$cmd_virt_copy_in -d $clone_domain_name \
  $work_dir/network \
  /etc/sysconfig/

# 複製後の仮想イメージを起動
$cmd_virsh start $clone_domain_name

echo "Clone $clone_domain_name has been started."
exit 0

sed だの awk だのを駆使してなんとか作った、という感じです(苦笑)。あとはこのファイルを clone-guest.sh という名前で保存し、実行権限を与え、パスの通ったディレクトリに置いておけば、
# clone-guest.sh AAA BBB

という感じで、既存の AAA という仮想環境を BBB という名前で複製して、各種設定ファイルまで書き換えて、起動する、という使い方ができるはずです。 ここまでできるようになると、KVM もかなり便利に使えてます。










 

このページのトップヘ