Create: 2014/09/24
LastUpdate: 2014/09/25
XenServer6.2の仮想化環境で、Webサーバの自動スケールアウトを試してみました。
動作確認では、JobScheduler1.7 を使用しています。
シナリオ
ロードバランサ(LVS)の背後にいるWEBサーバ(Web01)の負荷を監視します。想定以上の負荷を検知したら、自動的にスケールアウトしてWEBサーバを1台追加し、WEBサーバ2台で負荷分散することにします。
下図は、JobSchedulerを利用した実装イメージです。
青のボックスは常駐のVMです。グレーのボックスはスケールアウトされるVMです。
楕円は、ジョブです。黄色のボックスは、スクリプトです。
ジョブ①を定期的に実行して、Web01上のCPU監視スクリプトを実行します。CPU監視スクリプトが閾値をこえる負荷を検知すると、JobSchedulerの外部APIで、JobChain(②~⑧) を実行してスケールアウトします。
各ジョブの概要は以下のとおり。
内訳は、①が監視用、②~⑤がWeb02生成用、⑥~⑦がWeb02コンテンツの最新化、⑧がWeb02のユーザへの解放となります。
コンテンツの最新化は無くてもいいのですが、JITL(scp)の使用例として入れています。
① web01_watch
このジョブは、一定間隔で実行するようにスケジューリングし、JITL(ssh)を利用してWeb01 上の 「CPU監視スクリプト」を実行します。
「CPU監視スクリプト」は、Apache の server-status から CPUのロードアベレージを取得します。
ロードアベレージが、閾値をこえたら、JobScheduler の外部API(http)を利用してJobChainを実行します。
なお、スケールアウトは1回のみとします。
② web_scaleout
JITL(ssh)を利用してXenServerのdom0(管理コンソール)上の「VM生成&起動スクリプト」を実行します。
「VM生成&起動スクリプト」は、事前に用意しておいた WEBサーバのテンプレートからVMを生成し、ホスト名、IPアドレスなどのパラメータを xenstore に設定してVMを起動します。
テンプレートには、VM起動時に xenstore からパラメータを取得して、ホスト名、IPアドレスなどを設定するスクリプトを仕込んでおきます。なお、この時点ではNICをダウンさせておきます。
③ wait1
上記②は、Web02 の起動完了を待たずにジョブが終了するので、Web02の起動が完了するまで、このジョブでスリープします。
④ reboot
上記②のホスト名、IPアドレス変更などを有効にしてネットワークに接続するために、Web02 をリブートします。この時点では、Web02 のNICがダウンしていて接続できないので、dom0上 で xe コマンドを実行して Web02をリブートします。
⑤ wait2
上記④は、Web02 の起動完了を待たずにジョブが終了するので、Web02の起動が完了するまで、このジョブでスリープします。
⑥ scp_get
JITL(scp)を利用して、Web01からコンテンツをGETします。
⑦ scp_put
JITL(scp)を利用して、上記⑥で取得したコンテンツを Web02 にPUTします。
⑧ lvs_add
JITL(ssh)を利用して、LVS上の「LVSに追加スクリプト」を実行します。
「LVSに追加スクリプト」は、LVSの weight を0以上に設定して、ロードバランサからWeb02にリクエストが振り向けられるようにします。
XenServerの準備(テンプレート作成)
Web01 をコピーしてテンプレートを作成します。
XenServerのテンプレート作成手順は、「 [XenServer 6.1.0] VM作成時にIPアドレス設定」を参考にしてください。
テンプレートに仕込むスクリプトは、以下のとおり。
- [ファイル名] /etc/init.d/xe-install
#!/bin/bash # # xe-install setting hostname & network # # chkconfig: - 99 15 # description: setting hostname & network # processname: # config: # pidfile: # # Source function library. . /etc/rc.d/init.d/functions # groval variable prog=xe-install lockfile=/var/lock/subsys/xe-install RETVAL=0 LOG=/root/xe-install.log # alredy run if [ -e "${lockfile}" ]; then echo "ERROR: alredy run." >> ${LOG} exit ${RETVAL} fi # xenstore read XENSTOREREAD=`which xenstore-read` if [ ! -e "${XENSTOREREAD}" ]; then echo "ERROR: not found xenstore-read." >> ${LOG} exit ${RETVAL} fi NAME=`${XENSTOREREAD} vm-data/name` # hostname IP=`${XENSTOREREAD} vm-data/ip` # ip address GW=`${XENSTOREREAD} vm-data/gw` # default gateway NM=`${XENSTOREREAD} vm-data/nm` # netmask # start start() { touch ${lockfile} if [ "${NAME}" != "" ]; then /bin/cp -p /etc/sysconfig/network /etc/sysconfig/network.ORG cat << EOF > /etc/sysconfig/network NETWORKING=yes HOSTNAME=${NAME} EOF echo "create /etc/sysconfig/network" >> ${LOG} else echo "ERROR: unknown vm-data/name." >> ${LOG} fi if [ "${IP}" != "" -a "${GW}" != "" -a "${NM}" != "" ]; then /bin/cp -p /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.ORG cat << EOF > /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 BOOTPROTO=none GATEWAY=${GW} IPADDR=${IP} NETMASK=${NM} ONBOOT=yes TYPE=Ethernet NM_CONTROLLED=no IPV6INIT=no IPV6_AUTOCONF=no USERCTL=no EOF echo "create /etc/sysconfig/network-scripts/ifcfg-eth0" >> ${LOG} else echo "ERROR: unknown vm-data/ip or vm-data/gw or vm-data/nm." >> ${LOG} fi } # case "$1" in start) start ;; stop) ;; *) echo $"Usage: $prog {start|stop}" RETVAL=2 esac exit ${RETVAL}このスクリプトは、xenstore から入力したパラメータをもとに、/etc/sysconfig/network と /etc/sysconfig/network-scripts/ifcfg-eth0 を作り直します。ただし、1回実行するとロックファイルを作成して、2回目以降は処理をおこないません。
VM起動時に自動実行するように設定します。
# chmod +x /etc/init.d/xe-install # chkconfig --add xe-install # chkconfig xe-install onスクリプトを設置したら、下記の名称でテンプレート化します。
- [テンプレート名] DEVWEB00
XenServer準備(dom0のスクリプト作成)
上記テンプレートからVMを生成して起動するスクリプトを用意します。
スクリプトの内容は以下のとおり。
- [ファイル名] /root/tools/vm_install.sh
#!/bin/bash # 引数チェック if [ $# -ne 4 ]; then echo "usage: $0 hostname ip gateway netmask" exit 1 fi TEMPLATE_NAME=DEVWEB00 HOSTNAME=$1 IP=$2 GW=$3 NM=$4 # テンプレートからVM作成 UUID=`xe vm-install template=${TEMPLATE_NAME} new-name-label=${HOSTNAME}` if [ "${UUID}" = "" ]; then echo "ERROR: vm-install" exit 1 fi echo "vm-install ok." # xenstore にパラメータを設定する # ここで設定した値をVMの /etc/init.d/xe-install で参照する xe vm-param-set uuid=${UUID} xenstore-data:vm-data/ip=${IP} if [ $? -ne 0 ]; then echo "ERROR: vm-param-set ip" exit 1 fi xe vm-param-set uuid=${UUID} xenstore-data:vm-data/gw=${GW} if [ $? -ne 0 ]; then echo "ERROR: vm-param-set gw" exit 1 fi xe vm-param-set uuid=${UUID} xenstore-data:vm-data/nm=${NM} if [ $? -ne 0 ]; then echo "ERROR: vm-param-set nm" exit 1 fi xe vm-param-set uuid=${UUID} xenstore-data:vm-data/name=${HOSTNAME} if [ $? -ne 0 ]; then echo "ERROR: vm-param-set name" exit 1 fi echo "vm-param-set ok." # VM起動 xe vm-start uuid=${UUID} if [ $? -ne 0 ]; then echo "ERROR: vm-start" exit 1 fi echo "vm-start ok." exit 0このスクリプトは、上記で作成したテンプレートからVMを生成し、xenstore にパラメータをセットした後、VMを起動します。パラメータにセットする値は引数で指定します。
ジョブから実行できるように、実行権限をつけます。
# chmod 777 /root/tools/vm-install.sh
LVSの準備
LVSはロードバランサです。LVS(NAT)については、「 [CentOS6][IPVS+Keepalived] 異なるサブネットでWEBサーバの負荷分散(NAT)」と「 [TIPS][CentOS 6] iptables で IPマスカレード と DNAT を設定する」を参考にしてください。
まず、Keepalived は、Web01とWeb02に負荷分散するように設定します。
ただし、Web02 は、Weight を "0" にして、リクエストを振り向けないようにします。
以下は、LVSの状態です。
# ipvsadm -l IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.90:http rr -> 10.0.0.91:http Masq 10 0 0 -> 10.0.0.92:http Masq 0 0 0次に、LVSのweight を変更するスクリプトを用意します。
内容は以下のとおり。
- [ファイル名] /root/tools/lvs_add.sh
#!/bin/bash # 引数チェック if [ $# -ne 2 ]; then echo "usage: $0 ip weight" exit 1 fi HOST=$1 WEIGHT=$2 # LVS の振り分け開始 ipvsadm -e -t 192.168.1.90:80 -r ${HOST}:80 -m -w ${WEIGHT} if [ $? -ne 0 ]; then echo "ERROR: ipvsadm" exit 1 fi # LVS のステータス表示 echo ipvsadm -l exit 0このスクリプトは、引数で指定したIPアドレスの weight を変更します。
ジョブから実行できるように実行権限をつけます。
# chmod 777 /root/tools/lvs_add.sh
Web01の準備
Apache の server-status を利用して負荷を監視するスクリプトを用意します。
まず、server-status を利用できるように Apache の設定を変更します。
ExtendedStatus On
<Location /server-status> SetHandler server-status Order deny,allow Deny from all Allow from 127.0.0.1 </Location>以下のように server-status が見れれば Apache の設定はOKです。
# curl -s http://127.0.0.1/server-status?auto Total Accesses: 60454 Total kBytes: 8007 CPULoad: .0654992 Uptime: 32321 ReqPerSec: 1.87042 BytesPerSec: 253.679 BytesPerReq: 135.627 BusyWorkers: 1 IdleWorkers: 19 Scoreboard: .__..._.._....._...._......._...................._.......................................................W.........................._......_...._.._._..._._................_........................_.................................._._.....................監視スクリプトの内容は、以下のとおり。
- [ファイル名] /root/tools/check_perf.sh
#!/bin/bash # 引数チェック if [ $# -ne 1 ]; then echo "usage: $0 limit" exit 1 fi LIMIT=$1 LOCK=/root/tools/add_web02.lock ADD_WEB02_XML=/root/tools/add_web02.xml JETTY_URL=http://192.168.1.61:40444/jobscheduler/engine-cpp # Apache server-status から CPU Load の値を取得 CPU_LOAD=`curl -s http://127.0.0.1/server-status?auto \ | grep 'CPULoad'\ | awk -F: '{printf("%f",$2)}'` # CPU Load が Limit をこえていないかチェック echo if [ "${CPU_LOAD}" != "" ]; then # 値の比較 okng=`echo "${CPU_LOAD} > ${LIMIT}" | bc` if [ ${okng} -eq 1 ]; then # Limit をこえたのでエラーメッセージ echo "ERROR: `hostname` : CPU LOAD=${CPU_LOAD}" # Web02 を追加済みなら処理しない if [ ! -e "${LOCK}" ]; then # JobSchedule に Web02 を追加する Order を登録 touch ${LOCK} curl -s ${JETTY_URL} -X POST -d @${ADD_WEB02_XML} if [ $? -ne 0 ]; then echo "ERROR: add web02" exit 1 fi fi exit 0 fi fi echo "SAFE: `hostname` : CPU LOAD=${CPU_LOAD}" exit 0このスクリプトは、Apacheのserver-statusで取得したCPULoad の値が、引数で指定された閾値を超えると、スケールアウト用のJobChainを実行します。なお、2回目以降の閾値越えは、検知してもJobChainは実行しません。
JobShedulerのJettyにPOSTする外部APIコマンドは以下のとおり。
JonChain の即時実行を指示します。
- [ファイル名] /root/tools/add_web02.xml
<add_order job_chain="/AutoScale/AddWeb02" id="add_web02"/>
JobSchedulerの準備(監視Jobの作成)
Web01の監視スクリプトを実行するジョブの定義は以下のとおり。
[ファイル名] web01_watch.job.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <job stop_on_error="yes"> <params> <param name="host" value="192.168.1.91"/> <param name="user" value="root"/> <param name="password" value="p@ssw0rd"/> <param name="auth_method" value="password"/> <param name="command" value="/root/tools/check_perf.sh 0.3"/> </params> <script language="java" java_class_path="" java_class="sos.scheduler.job.JobSchedulerSSHJob"/> <run_time> <period absolute_repeat="00:05" begin="07:00" end="22:00"/> </run_time> </job>このジョブは、毎日7:00~22:00の間に5分間隔で実行するようにスケジューリングします。
JITL(ssh)を利用してWeb01の監視スクリプト(check_perf.sh) を実行します。
監視スクリプトの引数の閾値(0.3) はテスト用に低く設定しています。
JobSchedulerの準備(スケールアウトJobChain の作成)
スケールアウト用のJobChainの定義内容は以下のとおり。
- [ファイル名] AddWeb02.job_chain.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <job_chain title="Web Server02 Auto Scaleout"> <job_chain_node state="JobChainStart" job="/sos/jitl/JobChainStart" next_state="web_scaleout" error_state="!error"/> <!-- 1.VM create-start & hostname,network setting--> <job_chain_node state="web_scaleout" job="ssh" next_state="wait1" error_state="!error"/> <job_chain_node state="wait1" job="wait" next_state="reboot" error_state="!error"/> <job_chain_node state="reboot" job="ssh" next_state="wait2" error_state="!error"/> <job_chain_node state="wait2" job="wait" next_state="scp_get" error_state="!error"/> <!-- 2.Content update --> <job_chain_node state="scp_get" job="scp" next_state="scp_put" error_state="!error"/> <job_chain_node state="scp_put" job="scp" next_state="lvs_add" error_state="!error"/> <!-- 3.LVS add --> <job_chain_node state="lvs_add" job="ssh" next_state="JobChainEnd" error_state="!error"/> <job_chain_node state="JobChainEnd" job="/sos/jitl/JobChainEnd" next_state="success" error_state="!error"/> <job_chain_node state="success"/> <job_chain_node state="!error"/> </job_chain>各stateで実行するジョブのパラメータを定義した設定ファイルは以下のとおり。
- [ファイル名] AddWeb02.config.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <?xml-stylesheet type="text/xsl" href="scheduler_configuration_documentation.xsl"?> <settings> <job_chain name="AddWeb02"> <order> <params/> <process state="web_scaleout"> <params> <param name="host" value="192.168.1.20"/> <param name="user" value="root"/> <param name="password" value="p@ssw0rd"/> <param name="auth_method" value="password"/> <param name="command" value="/root/tools/vm_install.sh devweb02 10.0.0.92 10.0.0.90 255.255.255.0"/> </params> </process> <process state="reboot"> <params> <param name="host" value="192.168.1.20"/> <param name="user" value="root"/> <param name="password" value="p@ssw0rd"/> <param name="auth_method" value="password"/> <param name="command" value="xe vm-reboot vm=devweb02"/> </params> </process> <process state="wait1"> <params> <param name="wait_sec" value="30"/> </params> </process> <process state="wait2"> <params> <param name="wait_sec" value="60"/> </params> </process> <process state="scp_get"> <params> <param name="host" value="192.168.1.91"/> <param name="user" value="root"/> <param name="password" value="p@ssw0rd"/> <param name="auth_method" value="password"/> <param name="action" value="get"/> <param name="file_list" value="/var/www/html/index.html"/> <param name="local_dir" value="/home/jobs/data"/> </params> </process> <process state="scp_put"> <params> <param name="host" value="192.168.1.92"/> <param name="user" value="root"/> <param name="password" value="p@ssw0rd"/> <param name="auth_method" value="password"/> <param name="action" value="put"/> <param name="file_list" value="index.html"/> <param name="local_dir" value="/home/jobs/data"/> <param name="remote_dir" value="/var/www/html"/> </params> </process> <process state="lvs_add"> <params> <param name="host" value="192.168.1.90"/> <param name="user" value="root"/> <param name="password" value="p@ssw0rd"/> <param name="auth_method" value="password"/> <param name="command" value="/root/tools/lvs_add.sh 10.0.0.92 10"/> </params> </process> </order> </job_chain> </settings>JITL(ssh) を利用したジョブは以下のとおり。
パラメータは、JobChainの設定ファイルで定義することにしています。
- [ファイル名] ssh.job.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <job order="yes" stop_on_error="yes"> <params/> <script language="java" java_class_path="" java_class="sos.scheduler.job.JobSchedulerSSHJob"/> <monitor name="configuration_monitor" ordering="0"> <script java_class="sos.scheduler.managed.configuration.ConfigurationOrderMonitor" language="java"/> </monitor> <run_time /> </job>JITL(scp) を利用したジョブは以下のとおり。
パラメータは、JobChainの設定ファイルで定義することにしています。
- [ファイル名] scp.job.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <job order="yes" stop_on_error="yes"> <params/> <script language="java" java_class_path="" java_class="sos.scheduler.job.JobSchedulerSCPJob"/> <monitor name="configuration_monitor" ordering="0"> <script java_class="sos.scheduler.managed.configuration.ConfigurationOrderMonitor" language="java"/> </monitor> <run_time /> </job>sleep で待機するジョブは以下のとおり。
パラメータは、JobChainの設定ファイルで定義することにしています。
- [ファイル名] wait.job.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <job order="yes" stop_on_error="yes"> <params/> <script language="shell"> <![CDATA[ if [ "${SCHEDULER_PARAM_WAIT_SEC}" = "" ]; then exit 1 fi echo "waiting ${SCHEDULER_PARAM_WAIT_SEC} sec." sleep ${SCHEDULER_PARAM_WAIT_SEC} exit 0 ]]> </script> <monitor name="configuration_monitor" ordering="0"> <script java_class="sos.scheduler.managed.configuration.ConfigurationOrderMonitor" language="java"/> </monitor> <run_time /> </job>
動作確認
まず、上記設定を確認します。
XenServerの状況をXenCenter で表示すると下図のとおり。
テンプレート(DEVWEB00)とLVSサーバ(devlvs01)、Web01サーバ(devweb01)を見ることができます。
LVSの状況は以下のとおり。
Web02(10.0.0.92)の Weight が "0" なので、Web01(10.0.0.91) だけが仕事するようになっています。
# ipvsadm -l IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.90:http rr -> 10.0.0.91:http Masq 10 0 0 -> 10.0.0.92:http Masq 0 0 0JOCでジョブの状態を表示すると下図のとおり。
監視ジョブ(web01_watch) は定期的に実行されるようにスケジューリングされています。
JobChain は下図のように表示されます。
ここからは、動作を確認してみます。
監視ジョブ(web01_watch)は、スケジューリングされているので5分に1回、自動的に実行されます。
Web01の負荷が閾値以下の場合、監視ジョブの実行結果は下図のように表示されます。
ここで、ApacheBench を利用して Web01 に負荷をかけてみます。
1,000ユーザが同時に10,000 リクエストを実行します。
# ab -n 10000 -c 1000 http://192.168.1.90/監視ジョブが、閾値を超える負荷を検知すると、実行結果は下図のように表示されます。
赤枠は、外部APIコマンドのレスポンスです。
下図は、スケールアウト実行中のJOCです。
スケールアウトが完了すると、XenCenterは下図のように表示されます。
Web02(devweb02) が生成され起動しています。
ロードバランサの状態は以下のとおり。
Web01とWeb02の2台で負荷分散しています。
# ipvsadm -l IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.90:http rr -> 10.0.0.91:http Masq 10 0 106 -> 10.0.0.92:http Masq 10 1 107