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

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

タグ:clone

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 もかなり便利に使えてます。










 

git をコマンドラインから使って、ソースコードなどをリポジトリから clone する場合は、こんなコマンドを実行することになります:
# git clone https://git.xxx.com/git/Project/app.git

リポジトリにアクセスするのに ID とパスワードによる認証が必要な場合もあります。そんな時は URL の中に ID(username) を埋め込んで、こんな感じで指定します:
# git clone https://username@git.xxx.com/git/Project/app.git
Password: (ここでパスワードを聞かれるので入力)

ここまでは特別な情報ではないです。

さて、この ID がメールアドレスだったりすると、当然 @ 文字が含まれます。例えば ID が username@xxx.com の時に、上記コマンドをそのまま実行しようとして、
# git clone https://username@xxx.com@git.xxx.com/git/Project/app.git
のようなコマンドを実行するとエラーになります。このケースだと xxx.com@git.xxx.com というサーバーに対して、username という ID でリクエストをしていることになるからです。


ではどうするか? 正解は ~/.netrc というファイルを用意して、以下の内容を記述します:
machine git.xxx.com
login username@xxx.com
password XXXXXXXX

この状態で以下のコマンドを実行します。これで clone できます:
# git clone https://git.xxx.com/git/Project/app.git

GUI のツールだとこの辺り気にせずできるんだろうか? でもコマンドラインだとこういう裏ワザというか回避方法知ってないと、いざって時に戸惑うんだよね。


(2014/Dec/09 追記)
git 1.7.9 以降であれば git-credential を使うべき、という指摘をいただきました。

 

このページのトップヘ