OpenVZのネットワーク(3) veth + vlan + bridge

異なるネットワークのVEをひとつのHNで共存させる際には、VZのデフォルトのネットワークインターフェースであるvenetではなくvethを使用します。実際には、既存のリアルサーバを仮想環境に集約するという場面があると思います。

以下のような条件の場合を取り上げて、実際に設定してみます。
もちろん、対抗側にはVLANスイッチがあると仮定します。


HNのインターフェース   eth0

VE100のVEID 100
VE100のVLANのタグID 100
VE100のIPアドレス 192.168.100.2/24
VE100のVE内インターフェース eth0
VE100のveth veth100

VE200のVEID 200
VE200のVLANのタグID 200
VE200のIPアドレス 192.168.200.2/24
VE200のVE内インターフェース eth0
VE200のveth veth200

まず、VLANインターフェースのコンフィグファイルを作成します。
VLANのカーネルモジュールをインストールします。


[root@node1 ~]# modprobe 8021q

/etc/sysconfig/network-scripts/ifcfg-eth0.100


DEVICE=eth0.100
BOOTPROTO=static
IPADDR=192.168.100.2
NETMASK=255.255.255.0
ONBOOT=yes

/etc/sysconfig/network-scripts/ifcfg-eth0.200


DEVICE=eth0.200
BOOTPROTO=static
IPADDR=192.168.200.2
NETMASK=255.255.255.0
ONBOOT=yes

eth0はIPアドレスをつけずにインターフェースをアップだけしておきます。
(よって設定するHNにはリモートアクセス用のeth1が必要かもしれません。)

/etc/sysconfig/network-scripts/ifcfg-eth0


DEVICE=eth0
BOOTPROTO=static
IPADDR=0.0.0.0
ONBOOT=yes

ネットワークをリスタートします。


[root@node1 ~]# /etc/rc.d/init.d/network restart

最終的にVLANインターフェースとvethをブリッジさせる必要があるので、
ブリッジインターフェースを作成してVLANインターフェースを登録しておきます。


[root@node1 ~]# brctl addbr vzbr100
[root@node1 ~]# brctl addif vzbr100 eth0.0100
[root@node1 ~]# brctl addbr vzbr200
[root@node1 ~]# brctl addif vzbr200 eth0.0200

次にvethの設定をします。
まずはvethのカーネルモジュールをインストールします。


[root@node1 ~]# modprobe vzethdev

vethではHNのインターフェースにIPアドレスを設定していないので、IP forwardingの設定をします。また、proxy arpの設定をします。


[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0/forwarding
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0/proxy_arp

venet用に設定していたIPアドレスコメントアウトします。
「VETH」で設定されているMACアドレスは適当です。実MACアドレス・他のVEのveth設定とバッティングしないものを設定します。

/etc/sysconfig/vz-scripts/100.conf


#IP_ADDRESS="192.168.100.2"
VETH="veth100.0,00:12:34:56:78:9A,eth0,00:12:34:56:78:9B "

/etc/sysconfig/vz-scripts/200.conf


#IP_ADDRESS="192.168.200.2"
VETH="veth200.0,00:12:34:56:78:9C,eth0,00:12:34:56:78:9D "

VEを起動してvethインターフェースを立ち上げた後、forwardingと
proxy arpの設定をします。


[root@node1 ~]# vzctl start 100
[root@node1 ~]# ifconfig veth100.0 0

ブリッジに登録します。
「xx:xx:xx:xx:xx:xx」はHNのeth0のMACアドレスを入力します。


[root@node1 ~]# brctl addif vzbr100 veth100.0
[root@node1 ~]# ifconfig vzbr100 hw ether xx:xx:xx:xx:xx:xx
[root@node1 ~]# ifconfig vzbr100 0
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0.0100/forwarding
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0.0100/proxy_arp
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/veth100.0/forwarding
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/veth100.0/proxy_arp
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/vzbr100/forwarding
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/vzbr100/proxy_arp
[root@node1 ~]# ip route add 192.168.100.2 dev veth100.0

同じようにVE200も設定します。


[root@node1 ~]# vzctl start 200
[root@node1 ~]# ifconfig veth200.0 0
[root@node1 ~]# brctl addif vzbr200 veth200.0
[root@node1 ~]# ifconfig vzbr200 hw ether xx:xx:xx:xx:xx:xx
[root@node1 ~]# ifconfig vzbr200 0
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0.0200/forwarding
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0.0200/proxy_arp
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/veth200.0/forwarding
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/veth200.0/proxy_arp
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/vzbr200/forwarding
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/vzbr200/proxy_arp
[root@node1 ~]# ip route add 192.168.200.2 dev veth200.0

これで、VE内でリアルサーバのようにネットワークの設定をすれば、通信できると思います。


参考
http://wiki.openvz.org/Virtual_Ethernet_device

リソース制限

/etc/sysconfig/vz-scripts配下にあるリソース制限設定ファイルについて調べてみたときのメモです。自分なりにCでせこせこプログラムを書いて検証してみましたが、なにぶん勉強不足なので意味のわからない記述があるかもしれません。調査中となっているところは、そのうち調べようと思っています。

numproc 最大プロセス数 上限を超えると「Cannot allocate memory」。
numtcpsock 最大TCPソケット数 PF_INETドメイン and SOCK_STREAM タイプ。上限を超えるとsocket()実行時)にエラー「No buffer space available」
numothersock その他のソケットの最大ソケット数 上限を超えるとsocket()実行時)にエラー「Cannot allocate memory」
vmguarpage 最低保証メモリ 調査中
kmemsize 最大カーネルメモリサイズ ユーザプロセスはある程度のカーネルメモリ(low memory)を消費するらしい。だいたい16-50KBらしい。
tcpsndbuf TCP Socket Send Bufferサイズ setsockoptは制限以上に設定してもエラーを返さない。バッファに制限値以上書き込む際にfailcntにカウントされる。エラーになってもプログラムは終了しない。上限はバッファの総合。
tcprcvbuf TCP Socket Receive Bufferサイズ 同上
dgramrcvbuf datagram socket receive Bufferサイズ 同上
othersockbuf その他のソケットのBufferサイズ 調査中
oomguarpages OOM killの保証値 調査中
lockedpages mlock(2)の最大ページ数 上限を越えると、mlockが失敗する。RSS自体は確保できる。
privvmpages 総プライベートページ数 全てのプロセスの仮想メモリの総合が上限を越えると、mallocが失敗した。
shmpages IPC SHMのセグメントサイズの最大数 調査中
numfile 最大オープンファイル数 -
numflock 最大flock -
numpty 最大pty数 -
numsiginfo siginfo構造体の数 調査中
dcachesize ファイルシステム関連のキャッシュサイズ ファイル操作によってメモリ不足を生じて、アプリケーションにerrnoを返し、致命的なオペレーションの間にメモリ不足がおきないための制限らしい。
numiptent 最大iptablesチェイン数 「numiptent - 10」が作成できるチェイン数になる。上限を超えると「iptables: Memory allocation problem」とエラーになる。
physpages 物理メモリページ数 今のところ制限はできず、使用量を見ることができるだけ。将来実装予定。
cpuunits CPUの最低保障値 調査中
cpulimit CPUの使用率 調査中
cpus CPUの仮想化 VE内で確認できるCPU数を制限できる
meminfo VE内の/proc/meminfo 仮想化 vzctl-3.0.17以降はデフォルト値がprivvmpages:1
iptables iptablesモジュールの種類 -
diskspace ディスクサイズ -
diskinodes inode数 -
quotatime quotatime -
quotaugidlimit user/group Idの最大数 VE内でquotaを使用する場合はこの設定が必要
  • セマフォは各VEごとに分離されているが、設定ファイルでの制限はなかった。VE内の/proc/sys/kernel/semで値を変えることができた。セマフォの値を増やすとkmemsizeにカウントされた。
  • devel版kernelではVEごとのIOアカウンティング機能があるようなので、将来的にはIOスケジューラ機能が実装されるようだ。

ロックファイル

情報をまとめてからだと、いつまでたっても更新できないようなので、小出しに更新していきます。
久々にOpenVZで気づいたことメモ。

先日vzctrlでstop/startを繰り返したところ、いつまでたってもVEが起動できないことがありあました。
調べてみると、vzctrlコマンドはVE起動の際に/vz/lock配下にVE番号のロックファイルを作成しているようで、処理がバッティングすることを防いでいるようです。

具体的には、


[root@vz ~]# vzctrl start 117
[root@vz ~]# ls /vz/lock/117.lck
[root@vz ~]# cat /vz/lock/117.lck
10794
といった感じで、「VE番号.lck」というファイルを作成して、その内容はvzctrlコマンドのpidのようです。

検証している過程で以下のような状態になって、vzctrlコマンドが滞積してしまいました。


[root@vz ~]# pgrep -lf vzctl
10794 /usr/sbin/vzctl start 117
10798 /usr/sbin/vzctl start 117
10823 /usr/sbin/vzctl start 117

[root@vz ~]# /var/log/vzctl.log
2007-01-30T20:35:25+0900 vzctl : VE 117 : VE already locked
2007-01-30T20:36:29+0900 vzctl : VE 117 : VE already locked
2007-01-30T20:36:55+0900 vzctl : VE 117 : VE already locked
2007-01-31T09:18:29+0900 vzctl : VE 117 : VE already locked
2007-01-31T09:22:23+0900 vzctl : VE 117 : VE already locked
2007-01-31T09:34:47+0900 vzctl : VE 117 : VE already locked

滞積した原因はvzctrlコマンドをstraceしっ放しにしておいたので、killできないプロセスができてしまったということでした。

Rubyでスクリーンショットサーバ(2)

Mozshotさんがベースにしたという、このソースを読んでみることにします。
*1

まずは動かしてみます。ソースをダウンロード後、以下のスクリプトを用意します。


snap.rb


#!/usr/bin/ruby

ENV['DISPLAY'] = ':1.0'
require 'gtkmozembed'
require 'moz-snapshooter'

Gtk.init
MozSnapshooter.new
Gtk.main

以下のように実行してみます。



[root@devel ~]# Xvfb :1 -screen 0 1024x768x24 &
[1] 19816

_XSERVTransSocketOpenCOTSServer: Unable to open socket for inet6
_XSERVTransOpen: transport open failed for inet6/xxxxxxxxxx:1
_XSERVTransMakeAllCOTSServerListeners: failed to open listener for inet6
Could not init font path element /usr/X11R6/lib/X11/fonts/CID/, removing from list!

[root@devel ~]# ruby snap.rb
1
Wrote screenshot-thumb.png

Could not init font path element /usr/X11R6/lib/X11/fonts/CID/, removing from list!

なにやらエラーが出ていますが、とりあえあずは無視します。
カレントディレクトリにscreenshot-thumb.pngというファイルが作成されていれば成功です。

勉強の意味で、勝手にソースにコメントをつけてみました(snapshotメソッドのところはまだ理解してませんが。)。

moz-snapshooter.rb コメント付き


#
# MozSnapshooter
# Web site thumbnailer
#
# Copyright (C) 2005 Mirko Maischberger
# Released in the Public Domain
#
# From an idea by Andrew McCall -
# http://www.hackdiary.com/archives/000055.html

require 'gtkmozembed'


#########################################################
#
# MozSnapshooterクラス
# Gtk::Windowのサブクラス
#
#########################################################

class MozSnapshooter < Gtk::Window

#########################################
#
# initializeメソッド
#
#########################################

def initialize

# Gtk::Window.initialize(=Gtk::Window.new)を呼び出す
# 他のウィジェットを含むことのできる新しいコンテナ(トップレベルウィンドウ)を作成
super

# selfはGtk::Window.newでできたオブジェクト
# コンテナのタイトルとボーダーを設定
# Gtk::Window#title=
# Gtk::Container#border_width=

self.title="MozSnapshooter"
self.border_width = 1

# width,highを設定
self.resize(780, 570)

# プロファイルのパスを設定
# ENV['HOME'].mozillaディレクトリにRubyGeckoプロファイルを作成
# ENV['HOME']の最後にスラッシュがないので、~/配下ではなく
# username.mozillaというディレクトリがつくられてしまった…

Gtk::MozEmbed.set_profile_path(ENV['HOME'] + '.mozilla', 'RubyGecko')

# Gtk::Container#<<
# Gtk::Container#addと同じ。
# コンテナ(トップレベルウィンドウ)にMozEmbedウィジェットを追加 している

self << Gtk::MozEmbed.new

# childって何?おそらくコンテナに追加されたMozEmbedウィジェットのオブジェクトだろう
# chrome(クロム)のフラグをセットしている
# chromeはmozillaの外観などに対する設定ファイルがあるディレクトリ。
# ALLCHROMEなので、すべてのchromeをセットしている。
# http://cow.neondragon.net/index.php/415-Fun-With-Firefox-Chrome-Urls

self.child.chrome_mask = Gtk::MozEmbed::ALLCHROME

# Gtk::Widget#set_size_request
# MozEmbedウィジェットのサイズを指定
# なぜかコンテナより大きい

self.child.set_size_request(816,600)

# GLib::Instantiatable#signal_connect
# net_stopシグナルを受信したら、on_net_stopハンドラを呼び出す。
# net_stopシグナルはページをロードし終わったら、発生する。
# asincronouslyって何?

self.child.signal_connect("net_stop") { on_net_stop }

# http://www.ruby-lang.orgをロード

self.child.location = "http://www.ruby-lang.org"

# インスタンス変数
# net_stopシグナルを受信してスクリーンショットを取るまでの
# 時間を定義している(1/1000秒単位)

@countdown = 2

# destroyシグナルを受信したら、終了。
# The user is bored, let's quit.

self.signal_connect("destroy") do
$stderr.print "closing...\n"
Gtk.main_quit
end

self.show_all
end

#########################################
#
# on_net_stopメソッド
#
# 先にあったnet_stopシグナルのハンドラを定義
# 1/1000秒後、falseがリターンするまでブロックを実行。
# @countdownで定めた時間が経過したら、
# screenshotメソッドを実行。
#
#########################################

def on_net_stop
Gtk::timeout_add(1000) do
@countdown -= 1
if(@countdown > 0)
puts @countdown
true
else
screenshot
false
end
end
end

#########################################
#
# screenshotメソッド
#
# スクリーンショットを作成
#
#########################################

def screenshot

# Gtk::MozEmbed#parent_window
# 親のウィンドウ(コンテナ)を取得
gdkw = self.child.parent_window

# geometryを取得
x, y, width, height, depth = gdkw.geometry
width -= 16
pixbuf = Gdk::Pixbuf.from_drawable(nil, gdkw, 0, 0, width, height)
pixbuf = pixbuf.scale(320, 200, Gdk::Pixbuf::INTERP_HYPER)
pixbuf.save("screenshot-thumb.png","png")
puts "Wrote screenshot-thumb.png"
Gtk.main_quit
end

end

ブラウジング・スナップショットとも外部コマンドに頼ってませんし、ブラウジング終了のシグナルを受信してからスナップショットをとっているところがいいです。なんかPythonのソースと似てますね。Rubyの勉強が終わったらPythonにも手をつけてみようと思います。

次はいよいよMozshotさんのソースに挑戦してみようと思います。

*1:ちなみに私のプログラミングスキルは、
Perl->ハッシュのリファレンスがわかるくらい
C->しばらく使ってないとポインタを忘れる
Ruby->いきなりrailsに手を出して四苦八苦
という感じです。オブジェクト指向もよくわかっていません。
ですので、その程度のレベルということでお願いします。

Rubyでスクリーンショットサーバ(1)

最近リンク先のページを小さく表示しているサイトをよく見かけるのですが、興味があったので実装する方法を調べていました。私はてっきりクライアントのブラウザでレンダリングをさせているのだと思っていたのですが(すいません無知で)、スクリーンショットをサムネイル化したイメージファイルを表示しているとわかりました。

すでにいくつかサービス化もされているようです。


ページのサムネイルorキャプチャをWEB上で作るサービスまとめ:phpspot開発日誌


他には以下のようなサイトがありました。


http://thumbs.bookmacro.com/sample/
WebToJpeg
Blinky


無料のサービスがほとんどですので利用させてもらえばいいのですが、サーバ側で保持していないサムネイルを新たに生成するのにどうしても時間がかかるようでしたので、何とか自前でつくろうと思い方法を調べてみました。
システム構成は、サーバはLinux、できればOpenVZ上で、APIは勉強中のRuby、できればRuby On Railsでつくり、イメージはファイルにするのではなくMySQLに格納して、というのを考えていました。

すでに他に構築している人がいないかなと探してみた結果、見つけたのが以下のサイトです。Linuxを使って、言語はPerl/Python/PHP/Rubyと一同勢ぞろいです。ただ、はてなさんはスクリーンショットWindowsで行っているようです。


SimpleAPI の仕組みについて考察してみる - drk7jp
http://yamashita.dyndns.org/blog/generate-web-page-thumbnails-on-linux-server-without-x
http://www.res-system.com/item/603


Drk7jpさんをでは、Xvfbという仮想フレームバッファ(仮想Xサーバ)を立ち上げそこにブラウザを起動させ、スクリーンショットをとるという方法をとっています。参考にしてVZ上で試してみました。VEはとりあえずFedora Core3(パッチ提供止まってますが)で構築しました。
Xvfbってどこかで聞いたことがあるような…と思ったら、オープンギャラリーさんのOpenVZのVEでXサーバを立ち上げる手順でも使っていました。
仮想環境では使うことができない(サーバにもあまり必要がない)/dev/ttyを必要としないXサーバ、といったところでしょうか。

手順通りに構築するとすんなりスクリーンショットをとることができました。
ただ、VE環境だとX系のパッケージをインストールする際に、/dev配下のデバイスファイルがほとんどなくなってしまいます。こうなるとVEにsshできなくなりますし、ハードウェアノードからvzctl enter もできなくなります。おそらくどちらもVEの/dev/urandomが必要なのでしょう。どこかから/dev配下のファイルをコピーしてくれば復旧します。よく見るとオープンギャラリーさんでも説明していました。
また、Firefox1.5系を使っていると何回かサイトを表示させていると不具合があったので、2.0系に変更しました。

この方法で一応できたのですが、以下の点に不満がありました。

  • 連続してキャプチャすると、レンダリングがキャプチャに追いつかず、前のページ(もしくはレンダリング途中のページ)がキャプチャされるので、sleepをいれなければならない
  • 外部コマンドを呼び出していてかっこ悪い
  • ブラウザ(Firefox)を常駐させているので、大量にメモリ食っている


Weboo!Returnsさんではgtkmozembedというライブラリを使っていて、外部コマンドを呼び出しておらず、これが理想です。ただ、言語がPythonなのがネックです。
Rubyで同じことができないかと調べてみると、Rubyにもgtkmozembedライブラリが提供されていることがわかりました。

マニュアルがほとんど英語ですし、Rubyの基礎も怪しいのに背伸びしすぎかなと躊躇していると、すでにBlinkyさんがgtkmozembedを利用してRubyで構築していることがわかりました。しかもソースを全て公開しています。感謝です。

さっそくDBにバイナリデータを格納するように改造してみようと思います。まずは、このソースを読んで勉強します。

OpenVZのネットワーク(2)

OpenVZではvenetの他に、vethデバイスを使ってネットワークインターフェースを作成することができます。

venetとvethの違いについては以下のサイトでまとめてありますので、下手な翻訳で恐縮ですが、訳しておきます。

http://wiki.openvz.org/Differences_between_venet_and_veth

  • vethはVE内でブロードキャストすることができます。よってVE内でドメインブロードキャストやそのようなものを扱うDHCPサーバやsambaサーバといったものまで使うことができます。
  • vethにはいくつかのセキュリティの含意があります。よってHSP(ホスティングサービスプロバイダ)のような信頼できない環境では推奨できません。これはブロードキャストや、トラフィックスニッフィングやIPコリュージョンの可能性のためです。i.e. VEユーザーは実際イーサネット層の直接アクセスのようなものでイーサネットネットワークをだめにすることができます。
  • venetデバイスでは、ハードウェアノードの管理者のみがVEにIPを割り当てることができました。vethデバイスでは、ネットワークはVEで完全に設定することができます。VEは正しいゲートウェイ、IP/ネットマスクなどを設定することができ、ノードの管理者はどこにトラフィックが向かうかを選ぶことのみが可能です。
  • vethデバイスは他のデバイスをブリッジすることができます。たとえば、ホストのシステム管理者は2つのVEのvethをあるVLAN eth0.Xにブリッジすることができます。このケースでは、2つのVEはこのVLANに接続することができます。
  • venetデバイスはvethデバイスと比べ少しだけ高速でより効率的です。
  • vethデバイスでは、IPv6は自動的にMACからアドレスを生成します。


vethは「venet0という気持ち悪いインターフェースを使わずに、eth0というインターフェースを使いたい」という場合や、VLANを切りたいときに有効だと思います。

DSRをしてみた(2)

前回はロードバランサ、リアルサーバともVIPをインターフェースに設定したのですが、iptablesを使うことでその必要がなくなるそうです。

前回の設定を一度全てはずした上で、それぞれのマシンで以下のコマンドを実行しました。


[root@lb ~]# ipvsadm -A -t 10.233.11.24:http -s rr
[root@lb ~]# ipvsadm -a -t 10.233.11.24:http -r 10.233.11.22:http -g
[root@lb ~]# iptables -t mangle -A PREROUTING -d 10.233.11.24 -j MARK --set-mark 1
[root@lb ~]# ip rule add prio 100 fwmark 1 table 100
[root@lb ~]# ip route add local 0/0 dev lo table 100
[root@lb ~]# iptables -t mangle -A POSTROUTING -d 10.233.11.24 -j TOS --set-tos 2

[root@real ~]# iptables -t nat -A PREROUTING -m tos --tos 2 -j REDIRECT
私の環境ではクライアント(cl)からロードバランサ(lb)への通信ができませんでした。クライアント(cl)のarpテーブルを見てみると、VIP(10.233.11.24)のMACアドレスが取得できません。ロードバランサ(lb)は自分のインターフェースに振られていないVIP(10.233.11.24)のarp要求に応答していないようです。DSASさんのページを見てみると、VRRPのところでクライアントにルーティングの設定をしているようです。
というわけで、クライアント(cl)に以下の設定を追加しました。


[root@cl ~]# route add -host 10.233.11.24 gw 10.233.11.21
今度はうまく通信できました。

iptablesが何をしているかを理解するために、このページを読んでみたのですが、TOSをPOSTROUTINGではなくPREROUTINGでセットしてその後にTOSでルーティングさせれば、iptalesのルールを減らすことができるのでは?と思いました。

さっそくロードバランサの設定を一旦すべて解除して、設定し直してみました。


[root@lb ~]# ipvsadm -A -t 10.233.11.24:http -s rr
[root@lb ~]# ipvsadm -a -t 10.233.11.24:http -r 10.233.11.22:http -g
[root@lb ~]# iptables -t mangle -A PREROUTING -d 10.233.11.24 -j TOS --set-tos 2
[root@lb ~]# ip rule add prio 100 tos 2 table 100
[root@lb ~]# ip route add local 0/0 dev lo table 100

この設定では接続できませんでした。tcpdumpをしてみると、ロードバランサがVIP(10.233.11.24)のarp要求を出していました。loをtcpdumpしても何も流れていないようですので、ルーティングできていないということでしょうか。