2015年11月17日火曜日

コンテナ上でのipvsおよびiptablesの利用

従来の仮想マシンと比較して軽量な仮想環境としてコンテナが注目されている。コンテナによる仮想環境での特徴としてコンテナで同じカーネルを共有している点があげられる。

また、コンテナで実際のサービスを提供する場合、アプリケーションがほかにカーネルの提供する機能も必要となる。そこで今回はコンテナ環境下でのカーネルの機能の利用について実際に試してみた。

コンテナ上でのカーネルの機能の利用

  • コンテナではapacheやmysqlといったプロセスはそれぞれの名前空間で実行されている
  • カーネルはすべてのコンテナでホストのカーネルを利用している。
  • ipvsやiptablesはカーネル側の機能
コンテナではホストのカーネルを共有しそれぞれのコンテナごとに独立した名前空間を作成して、その上でユーザプロセスを実行することによって仮想環境を実現している。
しかし、実際のサービスなどにおいてはhttpサーバやデータベースなどのユーザプロセスだけではなくカーネルの機能も合わせて利用される。例えば、ロードバランスやパケットのフィルタリングを行う場合などはipvsやiptablesといったカーネルの機能が必要になる。
コンテナ上でカーネルの機能を利用する場合、そのカーネルについてはすべてのコンテナで共有されている点において通常とは事情が異なる。
今回はipvsおよびiptablesについて実際にコンテナ上での利用を試してみた。

システム構成

コンテナ構成図
検証としてWebサーバへのアクセスをLVSを利用してロードバランスするシステムをコンテナを利用して構成することを考える(上図)
ホストマシン2台からなる構成で、それぞれのホストにipvsを利用してWebコンテナへのロードバランスを実行するLVSコンテナ、 iptablesとapacheを使ってLVSからロードバランスされてきたアクセスを受けるWebコンテナを作成する。
以下の説明ではそれぞれのホスト及びコンテナの構成方法についての詳細は割愛して、それぞれのコンテナ上でのipvsおよびiptablesの動作および設定についてのみ述べる

LVSコンテナ

LVSコンテナは2つのネットワークインターフェースeth0、eth1を持ちそれぞれが仮想ブリッジを介して仮想イーサネット(veth)によってホストマシン(物理マシン)のインターフェースに接続されている。eth0側が外部ネットワーク、eth1側がWebコンテナ側のネットワークになっている。

ipvsの設定

ipvsの設定はコンテナ上にipvsadmによって可能である。ただし、ipvsadmを利用してコンテナ上でipvsの設定を実行する前にホスト側でip_vsモジュールをmodprobeにより読み込んでおく必要がある。

lvshost# modprobe ip_vs

またコンテナ上でipvsadmで設定を行うためにはコンテナがroot権限で実行されている必要がある。いわゆる一般ユーザ権限による非特権コンテナではipvsの設定はできないようである。
実際にipvs設定を行うと次のようになる。今回はロードバランスするアドレスは10.10.0.100としてeth0のエイリアスとして設定してある。またLVSのパケットの転送方式はダイレクトルーティング(-gオプション)を選択している。

lvs1 # ipvsadm -A -t 10.10.0.100:80 -s lc
lvs1 # ipvsadm -a -t 10.10.0.100:80 -r 192.168.0.50 -g
lvs1 # ipvsadm -a -t 10.10.0.100:80 -r 192.168.0.51 -g
lvs1 # ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.10.0.100:80 lc
  -> 192.168.10.50:80             Route   1      0          0        
  -> 192.168.10.51:80             Route   1      0          0

lvs1 # ip addr add 10.10.0.100 label eth0:100 dev eth0
lvs1 # echo 1 > /proc/sys/net/ipv4/ip_forward

 このようにipvsの設定方法は普通のLinux Boxの場合とほとんど変わらない。

Webコンテナ 

Webコンテナのネットワークインターフェースは仮想ブリッジを介して仮想イーサネット(veth)によりホストマシ ンのインターフェースに接続されている。ホストマシンのネットワークインターフェースは先ほどのLVSホストのeth1側のネットワークに接続するように 構成されている。
Webコンテナはアドレスが異なる(192.168.0.50と192.168.0.51)2つのものを作成して、それそれにLVSコンテナからロードバランスされたパケットが転送されてくるようにする。
 またここではWebコンテナは検証のためいわゆる非特権コンテナで実行されている。つまりWebコンテナ上のrootはホスト上からみた場合一般ユーザになっている。

Webコンテナの設定

コンテナ上でiptablesコマンドによりiptablesの設定は可能である。ただし、ipvsの場合と同様に設定を実行する前にホスト側でip_tablesモジュールをmodprobeにより読み込んでおく必要がある。

webhost# modprobe ip_tables

LVSでダイレクトルーティング方式での転送を選択しているので、ロードバランスされてWebコンテナに振り分けられてくるパケットの宛先はサービスアドレス(10.10.0.100)のまになっている。この宛先のパケットをWebコンテナで受け取るためにiptablesのREDIRECTターゲットを利用する。

root@web1# iptables -t nat -A PREROUTING -p tcp --dport 80 -d 10.10.0.100 -j REDIRECT

ipvsの場合と異なり、iptablesの設定はいわゆる非特権コンテナ上でも可能なようである。テスト時にWebコンテナの区別がつくようにindex.htmlにコンテナ名を記述しておく。

root@web1# echo "HELLO,This is web1" > /var/www/html/index.htm

同様にもう一つのWebコンテナ(web2)をipアドレスを変えて(192.168.0.51)作成する。本来であれば同じホストにコンテナ複数作成してロードバランスしたとしても負荷分散や冗長化という観点からはあまり意味がないが検証用にそのように構成した。

2つのWebコンテナを構成後、LVSコンテナのeth0側からサービスアドレスにアクセスするとロードバランスされていることが確認された。

$ curl http://10.10.0.100
HELLO,This is web1
$ curl http://10.10.0.100
HELLO,This is web2

コンテナ上のiptables/ipvsの設定について

作成した2つのWebコンテナでiptablesの設定を表示すると確認できるが、それぞれのコンテナで設定が独立していることがわかる。ipvsの設定も同様にコンテナごとに独立したものになる。

コンテナ上のiptablesのLOGターゲットについて

コンテナ上の場合、iptablesのLOGターゲットでのログの取得はできないようである。
これは一般的なカーネル空間のログの問題のようで、今のところカーネル空間のログをそれぞれのコンテナに関連するログごとに分けて記録するような仕組みがない。つまりコンテナからカーネル空間のログを記録する動作をした場合、すべてのコンテナおよびホストで共通になっている場所に記録される(あまり自信がありませんが)。その関係上、コンテナ上ではiptablesを利用してLOGターゲットでカーネル空間にログを出力することは、今のところ意図的にできないようになっているようである。
コンテナ上でiptablesのログを記録するには、ユーザ空間にログを出力するULOGやNFLOGターゲットを利用する必要がある。

その他

検証はしていないが、コンテナ上にインストールして実行するipvsadmやiptablesのコマンドはホストのカーネルバージョンとの互換性がある必要があるとおもわれる。

まとめ

  • iptableやipvsはコンテナでも同様に利用できる。
    • ただしホスト側での事前のモジュールの読み込みが必要である。
    • またipvsはroot権限でコンテナを実行していないと設定できない。
  • iptablesでログを取得したい場合はULOGかNFLOGを使うこと。 
これらの点に注意すれば、普通のLinux Boxと同様な方法で、コンテナを利用したロードバランサやファイアフォールをipvsやiptablesを使って構築することが可能である。ただしコンテナの場合、オーバーヘッドがなどの観点から、スループットなどについて別途検証が必要あると思われる。

参考文献
  • コンテナ上でiptablesのログターゲットが利用できない背景(カーネル空間のログの問題)はここを参考にさせていただきました。
  • 非特権コンテナについてはこちらを参考にさせていただきました(日本語約ページ)



2015年11月13日金曜日

最近話題のLinuxコンテナを試してみました。

Linuxコンテナ

従来の仮想マシンと比較して軽量な仮想環境としてコンテナが注目されています。仮想マシンのようにまるごと物理マシンをエミュレーションした環境を用意するのでなく、ネットワークやプロセスIDなどのみが独立した環境(コンテナ)を用意してその中でプロセスを実行するタイプのOSレベル仮想環境です。今回はそのコンテナについてもっともベースとなる部分を試してみました。

LXCのインストール

最近はコンテナの利用する環境を整えるのにも各ディストリビューションに対応したパッケージをインストールするだけで大丈夫です。 コンテナを利用するための実装にはいくつのかの種類がありますが今回はLXCを利用します。他にはDockerなどが有名です。ただLinuxコンテナの核となる部分はカーネル側の機能なので、実装が違っていてもベースとなる考え方は一緒です。 
 今回はDebian/Jessieを利用しますが、LXCはほとんどのディストリビューションでパッケージ化されていると思います。またカーネルが必要な機能を有効にしてコンパイルされていないとダメなのですが、ディストリビューションの標準カーネルであればまず問題ないはずです。
 DebianのJessieではapt-getでインストールするだけです。
# apt-get install lxc

コンテナの作成 


 ではコンテナを作りましょう。一連の作業はrootで実行する必要があります。コンテナの作成においては、コンテナ用のファイルシステムを作成することがメインです。あとはコンテナを実行するためのLXCの設定ファイルも必要です。

コンテナ用のファイルシステム

コンテナ内のプロセスからはこのファイルシステムがルートファイルシステム(/)として見え、基本的にここにしかアクセスできません。コンテナ内のプロセスはこのルートファイルシステム上で実行されます。

 コンテナを作成するには先ほどインストールしたLXCのユーティリティに含まれるlxc-createコマンドを利用するのが簡単です。

lxc-create -n <コンテナ名> -t <テンプレート名> -- <テンプレートオプション>
例:test1という名前で64bit Debian/Jessieのルートファイルシステムを作成する場合 


# lxc-create -n test1 -t debian -- --arch amd64 --release=jessie

 lxc-create コマンドは-tオプションで指定したテンプレートに従ってルートファイルシステムを作成します。テンプレートは/usr/share/lxc/templates/lxc-<テンプレート名>という名前で実体が用意されています。必ずしもホストのLinuxと同じディストリビューションのファイルシステムでなくてはいけないということはありませんがアーキテクチャは同じでないといけません。ホストが64bitであるならばルートファイルシステムも64bit用のものを用意します。
 debianテンプレートの場合debootstrapコマンドを利用してネットワーク越しにrootfsを取得してきます

# lxc-create -n test1 -t debian -- --arch amd64 --release=jessie

 LXCの標準的な構成であればコンテナの保存場所は/var/lib/lxcになります。その場所の<コンテナ名>ディレクトリ以下にコンテナの一連のファイルが作成されます。

# cd /var/lib/lxc
# tree -L 1 test1
test1
|-- config
`-- rootfs
rootfs以下が作成されたルートファイルシステムです。configがLXCの設定ファイルになります。rootfs以下にはdebootstrapで作成された(最小インストールの)Debianのルートファイルシステムが作成されています。

# ls test1/rootfs/
bin   dev  home  lib64    mnt  proc  run     selinux  sys  usr
boot  etc  lib     media    opt  root  sbin  srv      tmp  var

 作成されたコンテナの一覧はlxc-lsコマンドで確認できます。

# lxc-ls -f
NAME   STATE    IPV4  IPV6  AUTOSTART 
-------------------------------------
test1  STOPPED  -     -     NO        

まだ、コンテナを作成しただけで実行してはいないのでSTOPPEDになっています。

コンテナの実行

早速コンテナを実行してみましょう。コンテナを実行するとはコンテナ内でなんからのプロセスを実行することです。実行するものはなんでも良いのですが、コンテナ内ではコンテナのルートファイルシステムにしかアクセスできませんので、その上にプロセス本体のファイルが存在していることが必要です。本体の実行にライブラリが必要なのであればそれも同様にルートファイルシステム上に存在している必要ががあります。

 デフォルトで作成される設定ファイルは内容がまだスカスカで実用には十分ではありませんがとりあえずコンテナ内でプロセスを実行することはできるはずです。

 ここではコンテナ内でシェル(bash)を実行してみます。コンテナ内でのプログラムの実行するにはlxc-startコマンドを利用します。

# lxc-start -n test1 -- /bin/bash
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell

 警告が出てしまいますが、シェルをコンテナ内で実行できました。このシェル上で色々確認してみるとこのシェルのプロセスがコンテナ内に閉じ込められていることがわかります。
 ファイルシステムは/var/lib/lxc/test1/rootfs以下の部分にしかアクセスすることができません。そこがコンテナ内ではルートファイルシステム(/)として見えていることが分かります。
root@test1:/# pwd
/
root@test1:/# ls
bin   dev  home  lib64  mnt  proc  run   selinux  sys  usr
boot  etc  lib   media  opt  root  sbin  srv      tmp  var
root@test1:/# cat /etc/hostname
test1

 psでプロセスを確認してみると自身のシェル(とpsコマンド)しか見えないのがわかります。他に大量に動作しているであろうホスト上のプロセスは確認できません。しかもシェルのPIDがなんと1番になっているのがわかります。

root@test1:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 05:12 ?        00:00:00 /bin/bash
root        11     1  0 05:40 ?        00:00:00 ps -ef

 ipコマンドでネットワークを確認してみるとネットワーク(eth0)がないことがわかります。ディストリビューションによってはLXCインストール時にコンテナ用のネットワークが用意され有効になっているかもしれません。いずれにしてもホスト上のものとは別のネットワークが見えるはずです。

root@test1:/# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

 シェルから抜けて終了させてみます。するとコンテナも終了します。lxc-lsコマンドで確認してみてもstoppedになっていると思います。コンテナでは基本的にコンテナ内でPID1番に見えるプロセスが終了するとコンテナ自身も終了します。

 再びコンテナ内でbashを実行して、ホスト上の別端末からlxc-lsコマンドで確認してみます。今度はステータスがRUNNINGになっていると思います。
# lxc-start -n test1 /bin/bash
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
root@test1:/#

別端末から
# lxc-ls -f
NAME   STATE    IPV4  IPV6  AUTOSTART 
-------------------------------------
test1  RUNNING  -     -     NO


 コンテナの外からコンテナ内で実行しているプロセスがどのようにみえているか確認してみます。bashだとどれがコンテナ内のbashなのか確認しにくいのでコンテナ内のbashから適当なプロセスをさらに実行して目印にしておきます。

root@test1:/# tail -f /dev/null

 ホスト上でpsコマンド実行するとコンテナ内のプロセスは普通に確認できることがわかります。プロセスIDも他のプロセスと同様のものがちゃんと振られています。コンテナ実行時のlxc-startが親プロセスとしてコンテナ内のbashを実行し、それがコンテナ内ではPID1番(外側では12146番)として扱われています。

# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
...
root     12142 12081  0 05:47 pts/1    00:00:00 lxc-start -n test1 /bin/bash
root     12146 12142  0 05:47 pts/1    00:00:00 /bin/bash
root     12156 12146  0 05:49 pts/1    00:00:00 tail -f /dev/null

まとめ

Linuxコンテナとは基本的にはこのようにプロセスを他のプロセスやファイルシステム、ネットワークなどから隔離して実行する技術です。隔離する事によりプロセスの予期せぬ動作からシステムや他のプロセスを保護することができます。