LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第21回LXCの構築・活用 [7] ─ いろいろなストレージバックエンドの利用(3:LVM)

前々回前回でAufs、Overlayfs、Btrfsと言ったファイルシステムをストレージバックエンドに使った場合のLXCの使い方を紹介してきました。

今回も引き続きLXCがサポートするストレージバックエンドの紹介をします。今回はLVMをストレージバックエンドとして使った場合のLXCの使い方を紹介します。

前回同様、LVMについてはここでは説明しません。

実は執筆時点でのLXCのstableリリースの最新である1.0.7ではストレージバックエンドとしてLVMを使う場合の不具合があるため、今回の実行例はUbuntu 14.04.2 LTSに以下のようにUbuntu LXCチームが作成しているstable-1.0 ブランチのデイリービルド用PPAのパッケージをインストールした環境で試しています。

$ sudo add-apt-repository ppa:ubuntu-lxc/daily-stable-1.0
$ sudo apt-get update
$ sudo apt-get install lxc
$ dpkg-query -s lxc | grep Version
Version: 1.0.7+stable~20150225-0206-0ubuntu1~trusty

1.0.8がリリースされたあとは、標準のLXCパッケージで今回の実行例がそのまま実行できるようになるはずです。

不具合については後で紹介します。

LXCでLVMをストレージバックエンドとして指定する場合、コンテナイメージを置くための論理ボリュームには以下の2通りのパターンがあります。

  • ボリュームグループ上に直接論理ボリュームを作成
  • Thin Provisioning用のプール上に論理ボリュームを作成

Thin Provisioning機能はLinux 3.2で導入された機能でLVMから使える機能です。

以下ではまずはThin Provisioning機能を使わない方法を紹介したあと、Thin Provisioning機能を使った方法を紹介します。

LVMをストレージバックエンドとして使う場合の準備

LVMをLXCのストレージバックエンドとして使う場合の特別な準備はありません。普通にLVMを操作するコマンド群があれば使えます。ここでは以降の実行例で使う環境で行った準備を紹介しておきます。

LVMを操作するコマンドはUbuntuでは"lvm2"というパッケージですので、これとLVMでサポートされているThin Provisioning機能を使う場合のツールをインストールしました。

$ sudo apt-get install lvm2 thin-provisioning-tools

まずはLVM用に設定したパーティションが必要ですね。ここの例では/dev/vdb1をLVM用に作成しています。

$ sudo fdisk -l /dev/vdb

Disk /dev/vdb: 8589 MB, 8589934592 bytes
2 heads, 1 sectors/track, 8388608 cylinders, total 16777216 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x8ee21092

   Device Boot      Start         End      Blocks   Id  System
/dev/vdb1            2048    16777215     8387584   8e  Linux LVM

/dev/vdb1を物理ボリュームとして設定します。

$ sudo pvcreate /dev/vdb1
  Physical volume "/dev/vdb1" successfully created

LXCではデフォルトではlxcという名前のボリュームグループを使います。このボリュームグループ名はlxc-createコマンド実行時のオプション、もしくはLXCのシステム設定ファイル(Ubuntuの場合/etc/lxc/lxc.confで設定できます。

ここではデフォルトで使われる名前を設定しました。

$ sudo vgcreate lxc /dev/vdb1
  Volume group "lxc" successfully created

これで準備ができました。

$ sudo pvs    (物理ボリュームの確認)
  PV         VG        Fmt  Attr PSize  PFree 
  /dev/vdb1  lxc       lvm2 a--   8.00g  8.00g
$ sudo vgs    (ボリュームグループの確認)
  VG        #PV #LV #SN Attr   VSize  VFree 
  lxc         1   0   0 wz--n-  8.00g  8.00g

LVMを使ったコンテナ用のシステム設定と作成時のオプション

LXCのシステム設定でストレージバックエンドとしてLVMを使う際の設定を行えます。これはコンテナ作成時のデフォルト値として使われます。この設定項目が設定されていない場合は表1に示したデフォルト値が使われます。

表1 LVM関係のシステム設定項目
設定項目 設定項目の説明 デフォルト値
lxc.bdev.lvm.vg デフォルトのLVMボリュームグループ名 lxc
lxc.bdev.lvm.thin_pool デフォルトのThin Provisioning用プール名 lxc

デフォルトではLXCのLVMストレージバックエンドは、lxc.bdev.lvm.thin_poolで設定した値の名前を持つThin Provisioning用のプールが存在する場合には自動的にそのプールに論理ボリュームを作成します。Thin Provisioning用のプールがない場合はlxc.bdev.lvm.vgで設定した値の名前を持つボリュームグループに論理ボリュームを作成します。

表1の設定項目を含め、コンテナ作成時にlxc-createコマンドに-B lvmを指定してLVMをストレージバックエンドとして使う場合に指定できるオプションがありますので紹介しておきましょう。指定しなければデフォルト値が使われます。

表2 ストレージバックエンドがLVMの場合に使えるオプション
オプション オプションの説明 デフォルト値
--lvname コンテナ用に使う論理ボリューム名 コンテナ名
--vgname コンテナ用に使うボリュームグループ名 lxc.bdev.lvm.vgの値
--thinpool Thin Provisioning用のプール名の指定 lxc.bdev.lvm.thin_poolの値
--fstype 論理ボリューム上に作成するファイルシステム名 ext3
--fssize 作成するファイルシステムのサイズ 1G

デフォルト値以外の名前を持つThin Provisioning用プールを使いたい場合は--thinpoolオプションでプール名を指定します。

デフォルト値以外のボリュームグループを使いたい場合は、--vgnameオプションでボリュームグループ名を指定します。Thin Provisioning機能を使う場合でも使用するボリュームグループ名はlxc-createコマンドが使う値(つまりlxc.bdev.lvm.vgの値もしくは--vgnameの設定値)と一致している必要があります。

特に指定しなければ論理ボリューム名はコンテナ名となります。コンテナ名以外の論理ボリュームを使いたい場合は--lvnameで指定します。

あらかじめOS上で作成しておくファイルシステムと違って、LVMの場合はコンテナを作成したりクローンしたりする際に論理ボリュームを作成しますので、その上に作成するファイルシステムの種類やサイズを指定します。

コンテナ作成の際にいろいろなファイルシステムを指定できますので、各種ファイルシステムの特長を活かせるかもしれませんね。

LVMを使ったコンテナの作成

まずはボリュームグループ上に直接論理ボリュームを作成するようにコンテナを作成してみましょう。

ここでは論理ボリューム上に2GBのext4ファイルシステムを作成する指定を行ってコンテナを作成してみます。

$ sudo lxc-create -n lvm01 -t download -B lvm --fstype ext4 --fssize 2G \
> -- -d ubuntu -r trusty -a amd64
     :(略)
  Logical volume "lvm01" created
     :(略)
You just created an Ubuntu container (release=trusty, arch=amd64, variant=default)
     :(略)
$ sudo lxc-ls -f
NAME   STATE    IPV4  IPV6  AUTOSTART  
-------------------------------------
lvm01  STOPPED  -     -     NO         

作成が成功しましたね。論理ボリュームの状態を確認してみましょう。

$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lvm01  lxc       -wi-a----    2.00g                                           

以上のようにコンテナ名で論理ボリュームが作成され、サイズは指定した通り2GBとなっています。

コンテナのディレクトリがどうなっているのか確認してみましょう。

$ sudo ls -F /var/lib/lxc/lvm01
config  rootfs/
$ sudo ls -F /var/lib/lxc/lvm01/rootfs
$ 

設定ファイルとrootfsディレクトリが存在しており、rootfs以下は空です。

設定ファイルを確認してみるとlxc.rootfsが論理ボリューム名になっています。それ以外は普通のコンテナと変わりありません。

$ sudo cat /var/lib/lxc/lvm01/config | grep -v "^#"

lxc.include = /usr/share/lxc/config/ubuntu.common.conf
lxc.arch = x86_64

lxc.rootfs = /dev/lxc/lvm01
lxc.utsname = lvm01

lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxcbr0
lxc.network.hwaddr = 00:16:3e:02:fb:36

ではコンテナを起動してみましょう。

$ sudo lxc-start -n lvm01 -d
$ sudo lxc-ls -f
NAME   STATE    IPV4        IPV6  AUTOSTART  
-------------------------------------------
lvm01  RUNNING  10.0.3.231  -     NO         

起動しましたのでlxc-attachコマンドを使ってコンテナ内でマウントの状況を確認するコマンドを実行してみます。

$ sudo lxc-attach -n lvm01 -- cat /proc/mounts | grep lvm01
/dev/lxc/lvm01 / ext4 rw,relatime,data=ordered 0 0
$ sudo lxc-attach -n lvm01 -- df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/lxc/lvm01  2.0G  383M  1.5G  21% /

/(ルート)として論理ボリューム/dev/lxc/lvm01がext4でマウントされており、サイズは指定した通りに2GBになっていますね。

このマウントはコンテナのマウント名前空間内で行われていますので、ホストOS上からは見えません。

$ sudo ls -F /var/lib/lxc/lvm01/rootfs (ホストOS上で実行)
$ grep lvm01 /proc/mounts (ホストのマウント名前空間からはマウントされていることは見えない)

LVMを使ったコンテナのクローン

それでは引き続いてLVMをストレージバックエンドに使っているコンテナのクローンを試しましょう。実は先に述べたLVMを使う場合の不具合はクローン機能の部分にあります。1.0.7ではバグがあり、LVMがストレージバックエンドの場合にコンテナのスナップショットを使ったクローンがエラーになってしまいます。

このバグは1.1.0では修正されています。またリポジトリでは1.0系列用ブランチであるstable-1.0ブランチでも修正は行われていますので、1.0.8がリリースされると修正がなされるはずです。

今回使っているstable-1.0ブランチのデイリービルドでも修正されていますので、コンテナは正常にクローンできます。

まずはlxc-clone-sオプションを指定せずにコンテナをクローンしてみましょう。

$ sudo time -p lxc-clone -o lvm01 -n lvm02
  Logical volume "lvm02" created
Created container lvm02 as copy of lvm01
real 13.48
user 2.23
sys 2.33

上記の例でも表示されているように、コピーによるスナップショットが行われていますので少し時間がかかります。この場合でもクローンされたコンテナのストレージバックエンドはLVMになります。

$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lvm01  lxc       -wi-a----    2.00g                                           
  lvm02  lxc       -wi-a----    2.00g                                           
$ sudo grep lxc.rootfs /var/lib/lxc/lvm02/config
lxc.rootfs = /dev/lxc/lvm02

それでは-sオプションを指定してスナップショットによるクローンを実行してみましょう。

$ sudo time -p lxc-clone -o lvm01 -n lvm03 -s -B lvm
  Logical volume "lvm03" created
Created container lvm03 as snapshot of lvm01
real 1.56
user 0.00
sys 0.05
$ sudo lxc-start -n lvm01 -d
$ sudo lxc-ls -f | grep lvm03
lvm01    RUNNING  10.0.3.135  -     -       NO         
$ sudo lxc-stop -n lvm01

スナップショットなのでlxc-cloneコマンドはすぐに終了しています。ここでクローン元のコンテナを起動して、終了させてみました。

lvsコマンドで論理ボリュームの状態を確認してみましょう。

$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lvm01  lxc       owi-a-s--    2.00g                                           
  lvm02  lxc       -wi-a----    2.00g                                           
  lvm03  lxc       swi-a-s--    2.00g      lvm01    0.02

lvsコマンドで確認するとスナップショットによるクローンでできたlvm03コンテナはLVMのスナップショット機能を使ったlvm01のスナップショットになっていることがわかります。

また、クローン後に元のコンテナを起動しましたので、ログ出力などが行われて、スナップショット時点からは少し変化しました。そのためスナップショット領域であるlvm03は少し容量が増えていますね。

もちろんlvm03コンテナも起動できます。

$ sudo lxc-start -n lvm03 -d
$ sudo lxc-ls -f
NAME     STATE    IPV4       IPV6  GROUPS  AUTOSTART  
----------------------------------------------------
    :(略)
lvm03    RUNNING  10.0.3.68  -     -       NO         

なお、LVMのスナップショットの仕組み上、スナップショットのスナップショットは作れませんので、スナップショットによるクローンで作成したコンテナをスナップショットを使ってクローンできません。

現時点では、前述の1.0系列のクローン以外にも、LVMをストレージバックエンドに使った場合の処理には少し不具合が残っています。

LVMのスナップショット機能を使ったクローンで作成したコンテナが存在する場合でも、以下のようにクローン元のコンテナが削除できてしまいます。この時、クローン元の論理ボリュームと共にスナップショットの論理ボリュームも同時に削除されてしまいます。しかしクローンにより作成したコンテナの設定ファイルとディレクトリは残ったままになります。しかし、当然コンテナイメージの実体である論理ボリュームは削除されてしまっていますので起動はしません。現時点では注意が必要です。

$ sudo lxc-ls
lvm01  lvm02  lvm03
$ sudo lxc-destroy -n lvm01  (スナップショット元のコンテナを削除)
     :(略)
  Logical volume "lvm03" successfully removed  (スナップショットのボリュームが削除された)
  Logical volume "lvm01" successfully removed

$ sudo lxc-ls
lvm02  lvm03  ("lvm03"ボリュームは削除されたがコンテナは表示される)

この例でlvm01を削除すると、同時にlvm03用の論理ボリュームであるlvm03が削除されますので、コンテナは起動できません。

この記事を書いている最中に筆者がこの不具合を修正するパッチを送付したところパッチがAckされました。まだマージはされていませんが、将来的にはこの問題はなくなるはずです。

Thin Provisioning機能を使っていない場合で、LVMのスナップショット機能を使ったクローンを行った場合、第19回で説明したaufsやoverlayfsを使ったクローンの際に使われていたコンテナの依存関係を表す仕組みと同じ仕組みを使い、クローン元のコンテナが削除できなくなる予定です。※1

Thin Provisioning 機能の利用

Linux kernel 3.2でDevice MapperにThin Provisioning機能が入りました。この機能をLVMから使用することで、実際のディスク容量以上の論理ボリュームを作成したり、スナップショット機能を使って既存の論理ボリュームのコピーを作成できます。

DockerをRHELやCentOS上で使う際には、このThin Provisioningの機能を使っていますのでご存じの方も多いのではないでしょうか。

LXCのLVMストレージバックエンドもこの機能をサポートしています。また、lxc-cloneを使ってクローンできます。

ここでは簡単にThin Provisioning機能を使ったコンテナの操作を紹介しておきます。Thin Provisioning機能そのものの詳細や、細かな操作についてはここでは述べません。

Thin Provisioning 機能を使う場合の準備

ボリュームグループは先の例で使ったlxcをそのまま使います。

$ sudo vgs
  VG        #PV #LV #SN Attr   VSize  VFree 
  lxc         1   0   0 wz--n-  8.00g  8.00g

Thin Provisioning用のプールをlxcという名前で作成します。データを保存する領域の他にメタデータを保存する領域が必要です。このためボリュームグループlxcの全部をプールとして確保するわけにはいきませんので、とりあえず90%程度を確保します。

$ sudo lvcreate -l 90%VG --type thin-pool --thinpool lxc lxc
  Rounding up size to full physical extent 8.00 MiB
  Logical volume "lxc" created
$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g               0.00                        

Thin Provisioning 機能を使ったコンテナの作成

ではコンテナを作成してみましょう。ここでは--thinpool lxcと指定して明示的にThin Provisioning用のプールを指定しています。

LXCはデフォルトではlxc.bdev.lvm.thin_poolで設定した名前のThin Provisioning用のプールがある場合は、自動的にそのプールを使ってコンテナ用の論理ボリュームを作成しますので、ここの例では--thinpool lxcは指定しなくても同じ結果となります。

逆にlxc.bdev.lvm.thin_poolで指定したプールがない場合に--thinpoolでプール名を指定せずに実行すると、Thin Provisioning機能は使わずに直接論理ボリュームを作成しようとします。少しややこしいですが、実際に使う場合はThin Provisioning機能を使ったり使わなかったりという状況は考えにくいので、あまり問題にならないかも知れませんね。

$ sudo lxc-create -n thin01 -B lvm --thinpool lxc \
> --fstype ext4 --fssize 8G -t download \
> -- -d ubuntu -r trusty -a amd64
     :(略)
  Logical volume "thin01" created
     :(略)
You just created an Ubuntu container (release=trusty, arch=amd64, variant=default)
     :(略)
$ sudo lxc-ls
thin01

作成できました。論理ボリュームの状態を見てみましょう。

$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g               7.13                        
  thin01 lxc       Vwi-a-tz-    8.00g lxc           6.41                        

7.2GBの領域に8GBのコンテナ用の論理ボリュームが作成されています。物理的に存在している容量以上のコンテナを作ったところでも、さらにコンテナを作れます。

$ sudo lxc-create -n thin02 -B lvm --thinpool lxc \
> --fstype ext4 --fssize 8G -t download \
> -- -d ubuntu -r trusty -a amd64
     :(略)
$ sudo lxc-ls
thin01  thin02  
$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g              14.14                        
  thin01 lxc       Vwi-a-tz-    8.00g lxc           6.41                        
  thin02 lxc       Vwi-a-tz-    8.00g lxc           6.31                        

7.2GBの領域に8GBのコンテナが2つ存在しています。これは極端な例ですが、常にコンテナに割り当てた容量を使わない場合もあるでしょうから、ある程度の領域を準備しておいて足りなくなりそうな場合に追加するという運用ができるようになりますね。

Thin Provisioning 機能を使ったコンテナのクローン

lxc-clone-sオプションを指定しないコピーによるクローンの場合は、コンテナを新たに作成する場合と変わりませんのでここでは述べません。では-sを指定してクローンしてみましょう。

$ sudo lxc-clone -o thin02 -n thin03 -s 
  Logical volume "thin03" created
Created container thin03 as snapshot of thin02
$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g              14.15                        
  thin01 lxc       Vwi-a-tz-    8.00g lxc           6.41                        
  thin02 lxc       Vwi-a-tz-    8.00g lxc           6.31                        
  thin03 lxc       Vwi-a-tz-    8.00g lxc  thin02   6.31                        

以上のようにthin02コンテナのスナップショットを使ったクローンとしてthin03コンテナが作成されました。

コンテナを2つ起動して、apt-get updateを実行してみました。

$ sudo lxc-start -n thin02 -d  (スナップショット元のコンテナの起動)
$ sudo lxc-start -n thin03 -d  (スナップショット先のコンテナの起動)
$ sudo lxc-ls -f
NAME    STATE    IPV4       IPV6  AUTOSTART  
-------------------------------------------
thin01  STOPPED  -          -     NO         
thin02  RUNNING  10.0.3.29  -     NO         
thin03  RUNNING  10.0.3.68  -     NO
$ sudo lxc-attach -n thin02 -- apt-get update  (コンテナ内でapt-get update実行)
     :(略)
$ sudo lxc-attach -n thin03 -- apt-get update  (コンテナ内でapt-get update実行)
     :(略)
$ sudo lvs  (論理ボリュームの確認)
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g              18.71                        
  thin01 lxc       Vwi-a-tz-    8.00g lxc           6.36                        
  thin02 lxc       Vwi-aotz-    8.00g lxc           8.43                        
  thin03 lxc       Vwi-aotz-    8.00g lxc  thin02   8.31                        

それぞれのコンテナで使用量が増えていますね。

先に紹介したThin Provisioning機能を使わないLVMでは、スナップショットのスナップショットが作成できませんでしたので、スナップショットを使ったクローンにより作成したコンテナをさらにスナップショットを使ってクローンできませんでした。

それに対してThin Provisioning機能を使うと、スナップショットのスナップショットが作成できます。試してみましょう。

$ sudo lxc-clone -o thin03 -n thin04 -s -B lvm
  Logical volume "thin04" created
Created container thin04 as snapshot of thin03
$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g              19.02                        
  thin01 lxc       Vwi-a-tz-    8.00g lxc           6.36                        
  thin02 lxc       Vwi-a-tz-    8.00g lxc           8.50                        
  thin03 lxc       Vwi-a-tz-    8.00g lxc  thin02   8.50                        
  thin04 lxc       Vwi-a-tz-    8.00g lxc  thin03   8.50                        

コンテナの削除を試してみましょう。クローン元のコンテナから削除してみます。

$ sudo lxc-destroy -n thin02 (クローン元のコンテナの削除)
     :(略)
  Logical volume "thin02" successfully removed
(削除したコンテナ用のボリュームのみ削除されている)
$ sudo lxc-ls
thin01  thin03  thin04  
$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g              16.53                        
  thin01 lxc       Vwi-a-tz-    8.00g lxc           6.36                        
  thin03 lxc       Vwi-a-tz-    8.00g lxc           8.50                        
  thin04 lxc       Vwi-a-tz-    8.00g lxc  thin03   8.50                        
(クローン先のボリュームは残っている)
$ sudo lxc-destroy -n thin03 (更にクローン元のコンテナの削除)
     :(略)
$ sudo lvs
  LV     VG        Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  lxc    lxc       twi-a-tz-    7.20g              16.52                        
  thin01 lxc       Vwi-a-tz-    8.00g lxc           6.36                        
  thin04 lxc       Vwi-a-tz-    8.00g lxc           8.50                        
(クローン先のボリュームは残っている)

Thin Provisioning機能を使った場合、スナップショットを使っても、それぞれの論理ボリュームは独立したコピーとなります。従って、クローン元を削除しても、クローン先のスナップショットが削除されることはありませんので、先に紹介したような不具合は起こりません。

まとめ

今回はストレージバックエンドとしてLVMを使った場合のコンテナの作成、クローンを紹介しました。

Thin Provisioning機能を使わない場合に不具合がある点を除いても、Thin Provisioning機能を使った方がスナップショットを使ったクローンを含めて便利な気がします。

ストレージバックエンドとしてLVMを使う場合は、カーネルがThin Provisioning機能に対応している場合はThin Provisioning機能の使用を前提にしても良いのかな、と記事を書きながら思いました。

次回もストレージバックエンドを便利に使う方法や、その他のまだ紹介していないLXCの機能を紹介する予定です。

おすすめ記事

記事・ニュース一覧