VPSで遊ぶ -その13 :Ubuntu鯖12.04にLXCをインスコして量産型仮想環境を構築する Part1:初期導入編

皆様こんにちは!
今日はLXC(Linux Containers http://lxc.sourceforge.net/ )(lxc version: 0.7.5)をUbuntu君に仕込んでみる実験を行います。
基本的にはここのヘルプ(https://help.ubuntu.com/12.04/serverguide/lxc.html )が充実してて殆どこれで事足りますが、日本語の情報が少ないので一応私も記録しときます。
(予め Ubuntu Weekly Recipe第226回 LXCで軽量仮想環境の活用 http://gihyo.jp/admin/serial/01/ubuntu-recipe/0226 も読んでおくといいかも)

Part1はインストール→とりあえず立ち上がる所まで。
Part2以降は需要と書く気があれば(LXC内でVPN環境構築・ファイル共有鯖構築等)。


ネットワーク周りをいじるのでイキナリVPSに仕込んでヒャッハーする前にまずは必ずローカルのVirtualBox内のクリーンでぶっ壊していいUbuntu12.04仮想環境等で十分に実験してみましょう。尚設定漏れによってトラブっても私は一切関知いたしません。全て自己責任で。



(2012/08/28追記)

VirtualBoxとLXCを同じ母艦上で同時に動かすと、VirtualBox内のOSを起動するときに母艦丸ごとフリーズするので、どちらか一方だけ動かすべし。(VirtualBox内で動いているUbuntu内に仕込んだLXCの起動は問題無し。母艦上に並列でVirtualBoxとLXCを起動させると再現)

フリーズ再現環境:

  • VirtualBox4.1.16、4.1.20
  • LXC lxc version: 0.7.5
  • 母艦Ubuntu 12.04 64bit (corei3,corei5マシンの実機2台で再現、VT-xをBIOSで有効かつVirtualBoxでも有効(VT無効での再現実験はまだ))

(2012/08/28追記おわり)



(さくらのVPS2G(vps)内LXC環境内Ubuntu(vpsvm0)にxrdpを入れてvpsvm0(のGUI環境)→sshトンネル→vpssshトンネル→Win7マシンのリモートデスクトップGUIをいじってる図)


A:インストール手順

Ubuntu 12.04だと超簡単で拍子抜け

手順1:lxcのインストール

root@xut:~# apt-get install lxc
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下の特別パッケージがインストールされます:
  bridge-utils cgroup-lite cloud-utils debootstrap euca2ools libapparmor1 libyaml-0-2 python-boto python-m2crypto python-paramiko python-yaml
提案パッケージ:
  btrfs-tools lvm2 qemu-user-static
以下のパッケージが新たにインストールされます:
  bridge-utils cgroup-lite cloud-utils debootstrap euca2ools libapparmor1 libyaml-0-2 lxc python-boto python-m2crypto python-paramiko python-yaml
アップグレード: 0 個、新規インストール: 12 個、削除: 0 個、保留: 0 個。
2,069 kB のアーカイブを取得する必要があります。
この操作後に追加で 13.6 MB のディスク容量が消費されます。
続行しますか [Y/n]?

(後略)

Y押してズバババっとインスコ完了。



手順2:isc-dhcp-serverのインストール
ぽこぽこ増やす量産型仮想環境を作るためにLXC環境用のdhcpサーバーを立てます。

root@xut:~# apt-get install isc-dhcp-server
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
提案パッケージ:
  isc-dhcp-server-ldap
以下のパッケージが新たにインストールされます:
  isc-dhcp-server
アップグレード: 0 個、新規インストール: 1 個、削除: 0 個、保留: 0 個。
429 kB のアーカイブを取得する必要があります。
この操作後に追加で 1,005 kB のディスク容量が消費されます。
取得:1 http://ftp.riken.jp/Linux/ubuntu/ precise-updates/main isc-dhcp-server amd64 4.1.ESV-R4-0ubuntu5.2 [429 kB]
429 kB を 0秒 で取得しました (701 kB/s)
パッケージを事前設定しています ...
Selecting previously unselected package isc-dhcp-server.
(データベースを読み込んでいます ... 現在 238643 個のファイルとディレクトリがインストールされています。)
(.../isc-dhcp-server_4.1.ESV-R4-0ubuntu5.2_amd64.deb から) isc-dhcp-server を展開しています...
man-db のトリガを処理しています ...
ureadahead のトリガを処理しています ...
isc-dhcp-server (4.1.ESV-R4-0ubuntu5.2) を設定しています ...
Generating /etc/default/isc-dhcp-server...
isc-dhcp-server start/running, process 13449
isc-dhcp-server6 stop/pre-start, process 13498

インストールは以上!なんて簡単なんでしょう。


B:設定ファイル探訪(全て母艦側)

lxcの設定ファイルはここらへんにあります。
1:/etc/lxc以下 (確認のみ)

root@xut:/# tree -F /etc/lxc
/etc/lxc
├── auto/   (自動起動コンテナ指定用ディレクトリ)
└── lxc.conf (lxc共通設定ファイル)

1 directory, 1 file
root@xut:/# cat /etc/lxc/lxc.conf
lxc.network.type=veth
lxc.network.link=lxcbr0
lxc.network.flags=up
root@xut:/#

特に弄る必要なし。



2:/etc/init/以下(確認のみ)

root@xut:/etc/init# ls|grep lxc
lxc-net.conf (lxc networkの起動スクリプト)
lxc.conf (lxcの起動スクリプト)
root@xut:/etc/init#

lxc-net.conf内では

echo 1 > /proc/sys/net/ipv4/ip_forward

だの

(中略)
# set up the lxc network
echo 1 > /proc/sys/net/ipv4/ip_forward
mkdir -p ${varrun}
brctl addbr ${LXC_BRIDGE}
ifconfig ${LXC_BRIDGE} ${LXC_ADDR} netmask ${LXC_NETMASK} up
iptables -A POSTROUTING -s ${LXC_NETWORK} -t nat -j MASQUERADE
dnsmasq -u lxc-dnsmasq --strict-order --bind-interfaces --pid-file=${varrun}/dnsmasq.pid --conf-file= --listen-address ${LXC_ADDR} --dhcp-range ${LXC_DHCP_RANGE} --dhcp-lease
-max=${LXC_DHCP_MAX} --dhcp-no-override --except-interface=lo --interface=${LXC_BRIDGE} || cleanup
touch ${varrun}/network_up
(後略)

だのiptables周りの設定を全部お任せでやってくれてます。特に弄る必要なし。

もしLXCコンテナ(192.168.40.50 port番号10222)に直接グローバルIPアドレスから接続したい時には

iptables -A PREROUTING -t nat -p tcp --dport 10222 -i eth0 -j DNAT --to 192.168.40.50:10222

等を上記 iptables -A POSTROUTING 行の次あたりにとりあえず追記して(+ ufw allow 10222 を母艦・LXCコンテナ双方で実行)しまえば良いでしょう。(192.168.40.50のLXCコンテナのsshのポートを10222番にしている場合等)



3:/etc/default/lxc (必要に応じて要編集)
LXCのネットワーク(ブリッジ接続)周りの設定ファイルです。

root@xut:/etc/default# cat lxc
# MIRROR to be used by ubuntu template at container creation:
# Leaving it undefined is fine
#MIRROR="http://archive.ubuntu.com/ubuntu"
# or
#MIRROR="http://<host-ip-addr>:3142/archive.ubuntu.com/ubuntu"

# LXC_AUTO - whether or not to start containers symlinked under
# /etc/lxc/auto
LXC_AUTO="true"

# Leave USE_LXC_BRIDGE as "true" if you want to use lxcbr0 for your
# containers.  Set to "false" if you'll use virbr0 or another existing
# bridge, or mavlan to your host's NIC.
USE_LXC_BRIDGE="true"

# If you change the LXC_BRIDGE to something other than lxcbr0, then
# you will also need to update your /etc/lxc/lxc.conf as well as the
# configuration (/var/lib/lxc/<container>/config) for any containers
# already created using the default config to reflect the new bridge
# name.
# If you have the dnsmasq daemon installed, you'll also have to update
# /etc/dnsmasq.d/lxc and restart the system wide dnsmasq daemon.
# LXC_BRIDGE="lxcbr0"
# LXC_ADDR="10.0.3.1"
# LXC_NETMASK="255.255.255.0"
# LXC_NETWORK="10.0.3.0/24"
# LXC_DHCP_RANGE="10.0.3.2,10.0.3.254"
# LXC_DHCP_MAX="253"

# LXC_SHUTDOWN_TIMEOUT=120

LXC_BRIDGE="lxcbr0"
LXC_ADDR="192.168.40.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="192.168.40.0/24"
LXC_DHCP_RANGE="192.168.40.2,192.168.40.254"
LXC_DHCP_MAX="253"

LXC_SHUTDOWN_TIMEOUT=120

root@xut:/etc/default#

特に弄る必要なし。(↑の例ではLXC_ADDR周りを弄ってます。)
(lxcbr0でブリッジ接続させたくない場合とか、LXC_ADDRを変えたい場合に編集)



4:/etc/default/ufw (必要に応じて要編集)
ライトなUbuntu使いなら ufwUbuntuのソフトウェアファイアウォール)を使ってiptalbesの設定をしている筈ですがufwが有効な場合は

DEFAULT_FORWARD_POLICY="ACCEPT"

にする必要があります。(この設定を忘れるとLXC_NETWORKの外に出れない)

# /etc/default/ufw
#
(中略)
# Set the default forward policy to ACCEPT, DROP or REJECT.  Please note that
# if you change this you will most likely want to adjust your rules
#DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_FORWARD_POLICY="ACCEPT"

(後略)

(設定しなおしたら要 service ufw restart)



5:/etc/dhcp/dhcpd.conf(要編集)
LXC_NETWORK用のDHCP鯖の設定はだいたいこんな感じ。(ここでは192.168.40.50〜192.168.40.120 を割り当てている)

ddns-update-style none;
option domain-name "lxc.local";
option domain-name-servers 192.168.40.1;
default-lease-time 600;
max-lease-time 7200;
authoritative;
log-facility local7;
subnet 192.168.40.0 netmask 255.255.255.0 {
 option routers 192.168.40.1;
 option subnet-mask 255.255.255.0;
 option broadcast-address 192.168.40.255;
 option domain-name lxc;
 option domain-name-servers 192.168.40.1;
 range dynamic-bootp 192.168.40.50 192.168.40.120;
 default-lease-time 7200;
 max-lease-time 43200;
}



6: /etc/default/isc-dhcp-server(要編集)

root@xut:~# cat /etc/default/isc-dhcp-server
# Defaults for dhcp initscript
# sourced by /etc/init.d/dhcp
# installed at /etc/default/isc-dhcp-server by the maintainer scripts

#
# This is a POSIX shell fragment
#

# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
#       Separate multiple interfaces with spaces, e.g. "eth0 eth1".
#INTERFACES=""
INTERFACES="lxcbr0"

INTERFACES="lxcbr0"に限定しておきましょう。
(設定しなおしたら要 service isc-dhcp-server restart)




7: /etc/dnsmasq.d/lxc(確認のみ)

root@xut:~# cat /etc/dnsmasq.d/lxc
bind-interfaces
except-interface=lxcbr0
root@xut:~#

C:LXCコンテナ(仮想環境)作成手順

コマンド一発流してあとは見てるだけ。(初回のみOSに必要なパッケージのダウンロードに十数分程度時間がかかります。2回目以降は爆速。)

(ubuntuの最新版12.04環境を vm0という名前で作成している例)

root@xut:~# lxc-create -t ubuntu -n vm0

No config file specified, using the default config
debootstrap は /usr/sbin/debootstrap です
Checking cache download in /var/cache/lxc/precise/rootfs-amd64 ...
installing packages: vim,ssh
Downloading ubuntu precise minimal ...
I: Retrieving Release
I: Retrieving Release.gpg
I: Checking Release signature
I: Valid Release signature (key id 630239CC130E1A7FD81A27B140976EAF437D05B5)
I: Retrieving Packages
I: Validating Packages
I: Retrieving Packages
I: Validating Packages
I: Resolving dependencies of required packages...
I: Resolving dependencies of base packages...
I: Found additional base dependencies: libbsd0 libedit2 libgpm2 libgssapi-krb5-2 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libpython2.7 libwrap0 openssh-client openssh-serv
er vim-runtime
I: Checking component main on http://archive.ubuntu.com/ubuntu...
I: Retrieving adduser
I: Validating adduser

(中略)

Processing triggers for resolvconf ...
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
invoke-rc.d: policy-rc.d denied execution of start.
Processing triggers for initramfs-tools ...
Download complete
Copy /var/cache/lxc/precise/rootfs-amd64 to /var/lib/lxc/vm0/rootfs ...
Copying rootfs to /var/lib/lxc/vm0/rootfs ...

##
# The default user is 'ubuntu' with password 'ubuntu'!
# Use the 'sudo' command to run tasks as root in the container.
##

'ubuntu' template installed
'vm0' created
root@xut:~#


インストールが終了すると /var/lib/lxc/vm0(vm0:任意の仮想環境名・コンテナ名) 以下にこんな感じで仮想環境のファイル群が作成されます。

root@xut:/var/lib/lxc/vm0# ll
合計 20
drwxr-xr-x  3 root root 4096  731 22:52 ./
drwxr-xr-x  3 root root 4096  731 22:41 ../
-rw-r--r--  1 root root 1249  731 22:52 config (vm0の設定ファイル)
-rw-r--r--  1 root root  110  731 22:52 fstab
drwxr-xr-x 22 root root 4096  731 22:51 rootfs/ (vm0のルートディレクトリ)

root@xut:/var/lib/lxc/vm0# find ./ -maxdepth 2
./
./fstab
./rootfs
./rootfs/opt
./rootfs/tmp
./rootfs/lib
./rootfs/proc
./rootfs/bin
./rootfs/sys
./rootfs/root
./rootfs/var
./rootfs/srv
./rootfs/home
./rootfs/sbin
./rootfs/lib64
./rootfs/media
./rootfs/selinux
./rootfs/run
./rootfs/dev
./rootfs/boot
./rootfs/mnt
./rootfs/etc
./rootfs/usr
./config
root@xut:/var/lib/lxc/vm0#

/var/lib/lxc/vm0/configでvm0固有の設定を記述します。(通常は特に編集の必要なし)

root@xut:/var/lib/lxc/vm0# cat config
lxc.network.type=veth
lxc.network.link=lxcbr0
lxc.network.flags=up
lxc.network.hwaddr = 00:16:3e:e5:9f:2e
lxc.utsname = vm0

lxc.devttydir = lxc
lxc.tty = 4
lxc.pts = 1024
lxc.rootfs = /var/lib/lxc/vm0/rootfs
lxc.mount  = /var/lib/lxc/vm0/fstab
lxc.arch = amd64
lxc.cap.drop = sys_module mac_admin
lxc.pivotdir = lxc_putold

# uncomment the next line to run the container unconfined:
#lxc.aa_profile = unconfined

lxc.cgroup.devices.deny = a
# Allow any mknod (but not using the node)
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
#lxc.cgroup.devices.allow = c 4:0 rwm
#lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm
#fuse
lxc.cgroup.devices.allow = c 10:229 rwm
#tun
lxc.cgroup.devices.allow = c 10:200 rwm
#full
lxc.cgroup.devices.allow = c 1:7 rwm
#hpet
lxc.cgroup.devices.allow = c 10:228 rwm
#kvm
lxc.cgroup.devices.allow = c 10:232 rwm
root@xut:/var/lib/lxc/vm0#

D:LXCコンテナ起動確認

ココで一旦母艦(親機)自体をリブートしましょう。
(リブート直後に母艦でifconfig -aしてみた図)

root@xut:~# ifconfig -a
eth0      Link encap:イーサネット  ハードウェアアドレス 08:00:27:fa:a8:39
          inetアドレス:192.168.25.40  ブロードキャスト:192.168.25.255  マスク:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  メトリック:1
          RXパケット:317 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:422 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:1000
          RXバイト:27923 (27.9 KB)  TXバイト:149231 (149.2 KB)

lo        Link encap:ローカルループバック
          inetアドレス:127.0.0.1  マスク:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  メトリック:1
          RXパケット:2 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:2 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:0
          RXバイト:100 (100.0 B)  TXバイト:100 (100.0 B)

lxcbr0    Link encap:イーサネット  ハードウェアアドレス 7a:33:e0:87:e5:0e
          inetアドレス:192.168.40.1  ブロードキャスト:192.168.40.255  マスク:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  メトリック:1
          RXパケット:0 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:2 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:0
          RXバイト:0 (0.0 B)  TXバイト:108 (108.0 B)

root@xut:~#

lxcbr0が新たに出来ています。(/etc/default/lxcを編集したので、この例ではinetアドレス:192.168.40.1 になっている。母艦のeth0は192.168.25.40)



インスコしたvm0を起動してみます。

起動は

lxc-start -n vm0

または

lxc-start -n vm0 -d

した後で

lxc-console -n vm0



シャットダウンは

lxc-shutdown -n vm0

強制停止は

lxc-stop -n vm0

です。
(起動した図)

(vm0環境でのifconfig -a)

root@vm0:~# ifconfig -a
eth0      Link encap:Ethernet  HWaddr 00:16:3e:e5:9f:2e
          inet addr:192.168.40.50  Bcast:192.168.40.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:17 errors:0 dropped:0 overruns:0 frame:0
          TX packets:11 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:1594 (1.5 KB)  TX bytes:1338 (1.3 KB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

root@vm0:~# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.40.1    0.0.0.0         UG    100    0        0 eth0
192.168.40.0    *               255.255.255.0   U     0      0        0 eth0
root@vm0:~#

dhcpでちゃんと192.168.40.50が割り当てられたようです。



(vm0を起動した後での母艦側のifconfig -a)

root@xut:/home/xut# ifconfig -a
eth0      Link encap:イーサネット  ハードウェアアドレス 08:00:27:fa:a8:39
          inetアドレス:192.168.25.40  ブロードキャスト:192.168.25.255  マスク:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  メトリック:1
          RXパケット:14750 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:7298 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:1000
          RXバイト:16875022 (16.8 MB)  TXバイト:2053244 (2.0 MB)

lo        Link encap:ローカルループバック
          inetアドレス:127.0.0.1  マスク:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  メトリック:1
          RXパケット:2 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:2 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:0
          RXバイト:100 (100.0 B)  TXバイト:100 (100.0 B)

lxcbr0    Link encap:イーサネット  ハードウェアアドレス 3a:9f:33:53:15:e1
          inetアドレス:192.168.40.1  ブロードキャスト:192.168.40.255  マスク:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  メトリック:1
          RXパケット:3144 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:11494 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:0
          RXバイト:160963 (160.9 KB)  TXバイト:16614801 (16.6 MB)

vethR9OLBE Link encap:イーサネット  ハードウェアアドレス 3a:9f:33:53:15:e1
          UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1500  メトリック:1
          RXパケット:3144 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:11501 エラー:0 損失:0 オーバラン:0 キャリア:0
          衝突(Collisions):0 TXキュー長:1000
          RXバイト:204979 (204.9 KB)  TXバイト:16615107 (16.6 MB)

root@xut:/home/xut#




(注意点)

初期ユーザー名:ubuntu、パスワードもubuntuなので
即効sudo adduser hogehogekun で別の任意のユーザーhogehogekunを作って
/etc/groupの
sudo:x:27:ubuntu
sudo:x:27:hogehogekunにしつつ
sudo deluser ubuntuしてしまいましょう。
あとはapt-get install ufw;ufw enable した後で好きに弄り回すべし。




lxc-*コマンドの一覧は
https://help.ubuntu.com/12.04/serverguide/lxc.html#lxc-admin あたりに書いてありますので一読してみればヨロシ



今日はここまで。Part2に続くかも。
(その前にバックアップ手順関連エントリーの完結編が先か…)

ではでは!

VPSで遊ぶ -その12 :darとmysqldumpをつかってサーバーの月次フルバックアップと日次差分バックアップを取る(中編:ローカル鯖側への転送スクリプトの巻)

みなさんこんにちは、本日は中編:ローカル鯖側への転送スクリプトの巻です。(前編:VPS鯖側はこちら)
転ばぬ先の杖。転んだ場合は次から転ばないようにすぐに対策しませう。(但し動作実験は丁寧に。)

ローカル鯖(手元側)スクリプト

ローカル(手元)側のバックアップスクリプトはこんな感じ。

スクリプト5:rootユーザーのcrontab -e

45 06 * * * /root/bin/backup_sasv01.sh >/backup/remote/sasv01.log 2>&1
#55 06 * * * /root/bin/backup_sasv02.sh >/backup/remote/sasv02.log 2>&1

実際はこのローカル(手元側)の自鯖自体のバックアップスクリプトも、前回エントリーのスクリプト1の様に走らせていますが説明では省略します。

VPS鯖の月次&日次のバックアップファイル達が3時-4時台に作成完了し、手元の自鯖自体のバックアップが完了した後の頃合いを見計らった早朝6時台頃に

/root/bin/backup_sasv01.sh 
/root/bin/backup_sasv02.sh

…といった/root/bin/backup_VPS側サーバー名.shを順次起動しています。

VPS鯖が増えバックアップ対象が増えた初回だけ、

  • mkdir -p /backup/remote/sasv01/dailybk;
  • mkdir -p /backup/remote/sasv01/dar_file;
  • chmod -R 600 /backup/remote/sasv01

の様にバックアップファイル設置用ディレクトリを手動で作成しています。

また、/backup/のパーミッション等も気をつけてください。(別鯖の情報が詰まった重要ファイルなのでchmod 600 /backupでroot以外不用意に見れないようにしておく)


ディレクトリ構成の図

/backup/local/以下が手元の自鯖のバックアップ保存領域
/backup/remote/VPS側サーバ名1/,/backup/remote/VPS側サーバ名2/ 以下がVPS鯖側の保存領域



スクリプト6:/root/bin/backup_VPS側サーバー名1.sh
古いファイルを削除してrsyncで転送してるだけです。(認証鍵のみでのssh接続等は既に出来る様になっている前提です。)
古いファイルを削除するか、それとも削除しないか。保存日数を何日にするか等は環境によって変わってくると思います。
私の場合ここから更に通常は繋いでいない外付けHDDに毎月1度接続してrsync(delete無し)させて取り外し、別の場所に保管しています。

(&最重要ファイルは物理的に離れた別の場所にも転送&別鯖にも暗号化して保管しています。
地震雷火事水害窃盗での破損紛失を考え重要度にもよりますが最高5重位の修復ルートにしています。)

#!/bin/sh
NOW=`date +%Y%m%d`
BASE_DIR="/backup/remote/sasv01/dailybk" #VPS側サーバ名のディレクトリに必ず書き換える
LOCALDAR_DIR="/backup/remote/sasv01/dar_file" #VPS側サーバ名のディレクトリに必ず書き換える
PORT="10222" #VPSサーバー側ポート番号、必ず書き換える。(標準22)
SVUSR="backupusr@74.xx.x.xxx" #VPSにssh接続するユーザー名@接続先 (sshユーザー名@IPアドレス)公開鍵認証でノーパスワード接続可能なバックアップファイル転送専用ユーザーを作成しておく。このユーザーの操作出来る範囲を限定したい場合はopenssh側設定で要調整(解説省略)
LOCAL_DIR="$BASE_DIR/$NOW" #ローカル(手元)側の日付毎ディレクトリ

#VPSサーバー側のバックアップ保存用ディレクトリが存在しない場合は、あえて自動で作らないで処理終了
if [ ! -e "$BASE_DIR" ]; then
  echo "$BASE_DIR not exists ,exit"
  exit 1
fi
if [ ! -e "$LOCALDAR_DIR" ]; then
  echo "$LOCALDAR_DIR not exists ,exit"
  exit 1
fi


#本日の日付yyyymmddのディレクトリが存在しない場合は生成、存在する場合はバッチ処理終了
if [ ! -e "$LOCAL_DIR" ]; then
  mkdir $LOCAL_DIR
else
  echo 'dir exists'
  exit 1
fi
cd $BASE_DIR

#96日以上昔にtouchされたdailybk配下の20*で始まるディレクトリは自動削除
#(VPS側では31日保管、手元側では95日保管)
#手元側ディスク容量を考え最低32日以上で各自要調整

find $BASE_DIR -mtime +96 -type d -name "20*"|xargs rm -rf


#96日以上昔にtouchされた*.darファイルは自動削除
find $LOCALDAR_DIR -mtime +96 -type f -name "*.dar"|xargs rm -f

#リモート(VPSサーバー側)からrsyncでmysqlのダンプファイルとdarのアーカイブファイルを受信
nice -n 19 rsync -auvz -e "ssh -p $PORT" $SVUSR:/backup/local/dailybk/ $LOCAL_DIR
nice -n 19 rsync -auvz -e "ssh -p $PORT" $SVUSR:/backup/local/dar_file/ $LOCALDAR_DIR
  1. 手元にある96日以上昔にtouchされたファイルは削除し、rsync(deleteオプション無し)でVPSサーバ側のみにあるファイルを転送しています。
  2. mysqlのダンプファイルはVPS側には最新1日分しかバックアップを吐いていないですが、ローカル側では96日保管としています。(96=3ヶ月とちょっと:数日分はマージン)
  3. 厳密には安全のために前回のスクリプト3を改造しVPS側に7日分、手元側に96日分保管の様にするほうが、手元側鯖落ちによるファイル転送漏れでのmysqldumpバックアップ歯抜けが防止できるので良いかと思います。
  4. 私の場合VPSのディスク容量が少ない&トランザクション無しの静的に近いCMSコンテンツしかない(但し鯖によってはDBの数は多いので容量は食う)&最悪でも月次+日次差分darの中に含まれている/var/lib/mysql/DB名/* からデータを戻せるためmysqldumpについてはこのようになっています。
  5. 前日エントリーのスクリプト3のコメントに従いVPS側にも3日分mysqlのダンプを保存するようにした場合には、毎日生成される /backup/remote/VPS鯖名/dailybk/yymmdd/ 以下に3日分のmysql_dailybk_mysqldump_yyyymmdd.tar.gz が保存されるようになります。VPS側・ローカル側の空き容量が十分にある場合には、コピーの多いこちらのほうがより安全です。



後編(バックアップ&転送動作確認&バックアップからの復元確認編)に続く。

今日は忙しいのでここまで。

VPSで遊ぶ -その11 :darとmysqldumpをつかってサーバーの月次フルバックアップと日次差分バックアップを取る(前編:VPS鯖側でダンプ作成の巻)

みなさまお久しぶりです。
どこかのレンサバ屋がデーターをふっ飛ばしたと聞きました。
怖いですねー、鯖屋なんて信用できませんねー、自衛が必要ですねー。

というわけで安鯖・VPS鯖を使う人々(に限らず皆々様)は自分の手元に必ず日次バックアップを持っておきましょう。
&出来れば通電しない環境、物理的に離れた環境にコピー→別の場所に数世代前まで定期的に保存もしておきましょう。HDDなんてタダみたいなもんだし。
私は東京大阪アメリカ西海岸に分散させています。
あと最重要データの数GBはパスワード付き圧縮&暗号化ディスクイメージに保存しそいつをmicrosdに入れて念には念を入れています。

自衛が重要、他人の安全宣言は信じるな。信じられるのは自分だけ。(割とマジ)

あんま関係ないけどHDD不調時はfsck掛ける前にddrescueでディスクイメージのクローンを作ったりしてディスクイメージ側を操作しましょうね…

ユルいユーザー向けバックアップスクリプト公開

データ流出も怖いけどデータ抹消もものすごく怖い。
というわけで今日は私の使っているVPS鯖内ファイルバックアップスクリプトを公開しておきます。

トランザクション処理等バリバリやっている鯖は対象ではありませんが、正しく運用することで全データロスト復旧不可\(^o^)/オワタを避けることは出来ると思います。(だいたい良い感じに戻せる但し完全完璧ではない、トランザクションデータはどっかに消えるかもねという感じ。)

(ついでに/etc以下とかはmercurialを導入して履歴管理もしておきましょう。鯖屋を信じず更新差分をいつでも取れるようにしておくことも重要だと思います。)

まあデーターロスト&バックアップネーヨで終わる会社なんて成り立ってるほうが奇跡ですわ。(ポールシフトで電磁波が云々地球の危機ガーとかそういうのは別ね。そうなったら紙とか石版とか口頭伝承しか残らないわ。)


ご注意:
このスクリプトの運用ミス・将来のプログラム構造の変化などでバックアップスクリプトが動かずデータを飛ばしても私は一切責任を持ちません。
差分バックアップが膨れてディスクフルでサーバーが停止しても知りません。
定期的にバックアップが動いているかの確認・復旧出来るかの確認・ディスク容量が足りているかの確認等はあなたの仕事です。

あくまでも手法の紹介ですので自己責任で。ここに書いてあることも鵜呑みにしないでください。

前提条件

基本的なインストールについては省略します。
また、手元(自宅等)にデータを転送するバックアップ用ユーザーはパスフレーズ無しの公開鍵認証でssh接続しますが、そちらの説明も省略します。(わからない場合は対象外ですので以下のスクリプトは使わないでください。)

  1. 一日一回の牧歌的なバックアップで良い。(リアルタイムにバックアップしたい・トランザクションバリバリな環境は対象外。閲覧主体のPHP-MySQLCMSを安物VPSに突っ込んだ環境を想定。)
  2. Linuxである。(Ubuntu 10.04LTS)
  3. sh(bash)が動く。
  4. コマンドラインPHPが動く。(呼び出し部分をPHPで作った俺俺スクリプトなので。)
  5. dar(ディスクアーカイブ)がインストールしてある。( aptitude install dar )
  6. 使っているDBはmysqlである。(postgresql対応版もあるけど今回は簡単のために省略)
  7. VPSサーバーの環境構築位自分で出来る。
  8. ローカル←→VPSサーバー間で公開鍵認証かつノーパスワードで接続できるバックアップファイル転送専用ユーザーを作成出来る。
  9. 手元(ローカル)または別会社にLinux鯖があってrsyncが出来る。
  10. 一ヶ月分の差分バックアップファイルを保存可能な十分なディスク空き容量がVPS側、ローカル側双方に存在する。

という要はVPSとか専用鯖の環境です。共有レンサバは対象外です。

dar (http://dar.linux.free.fr/) の概要についての説明も省きます。『dar バックアップ』でググって先人の解説ページを見てください。

darの何が嬉しいか簡単にいうと、

  • フルバックアップを1ファイルに圧縮して保存できる。
  • フルバックアップを元に差分バックアップを作成できるので日次では差分のみ作成して負荷を減らせる。
  • バックアップ対象外・圧縮対象外等の細かな制御が可能である。

ってことです。

概要

やってることは単純で図で書くとこんな感じ。

VPS鯖側で毎日(月一回フルバックアップ、毎日差分バックアップ)のバックアップファイルを生成して保存しておき

保存したファイルを手元のサーバー側起動でrsyncで取得する。(手元のサーバー起動なので、手元側が固定IPじゃなくてもOK)

  • crontab起動のPHPスクリプトからdarを使ってVPSサーバー内ファイルのバックアップ
  • mysqldumpを使ってDBのバックアップ
  • crontab起動のrsyncで手元側サーバーとバックアップファイルの同期

てなかんじです。


こんな感じで毎月1日にファイルのフルバックアップ、それ以外の毎日はフルバックアップからの差分バックアップしています。
差分バックアップは変化がなければ殆ど容量を食いません。


スクリプト

以下にスクリプトを貼っていきます。本日はVPSサーバー側。
手元側サーバーにrsyncする部分については中編・後編で。

ディスクのバックアップ自体はルートユーザーに実行させます。
(一般ユーザーの読める領域のみバックアップでいいなら好きにしてください。)

スクリプト1:VPSサーバー側、rootユーザーのcrontab -e

# m h  dom mon dow   command
10 03 * * * /root/bin/mysql-backup.sh >/dev/null 2>&1
40 03 1 * * /root/bin/dar_backup.php -f > /backup/local/dar.log 2>&1
40 03 2-31 * * /root/bin/dar_backup.php -d > /backup/local/dar.log 2>&1

mysql-backup.sh、dar_backup.php がバックアップスクリプトです。/root/bin/に設置しています。

をおこなっています。



スクリプト2:VPSサーバー側、バックアップファイル保存ディレクトリの作成スクリプト
このスクリプトはバックアップ環境構築時の初回のみ使います。

/root/bin/mkbackupdir.sh

#!/bin/sh
mkdir -p /backup/local/dailybk
mkdir -p /backup/local/dar_file
mkdir -p /backup/local/mysql
mkdir -p /backup/local/psql
chmod 600 /backup
chown -R backupusr:users /backup
chmod 600 /backup/local/dailybk
chmod 600 /backup/local/dar_file
chmod 600 /backup/local/mysql
chmod 600 /backup/local/psql

backupusrは、VPSサーバー側のバックアップファイルrsync用の専用ユーザーです。任意のユーザーを作ってください。
バックアップしたファイルはroot権限ユーザーとbackupusrのみが読み取り可能です。

/backupとしていますがこれは昔バックアップ用パーティションを分けていた名残です。特に深い意味はありません。
/backup/local/なのは相互バックアップをするときに /backup/remote/とか /bakup/相手鯖名/ とかの構成にする場合があるからです。



スクリプト3:VPSサーバー側、DB毎のmysqldump利用のダンプファイル生成スクリプト
(2012/06/25 VPS側にも3日分保存する場合の例をコメントアウトして追加&$BACKIDIRを毎回ディレクトリ削除するのをやめて$BACKIDIR内ファイル削除に変更)
/root/bin/mysql-backup.sh (要chmod 700)

#!/bin/bash
PATH=/usr/local/sbin:/usr/bin:/bin
NOW=`date +%Y%m%d`

# バックアップ先ディレクトリ
BACKDIR=/backup/local/mysql  #空白不可注意
DAILY_BACKDIR=/backup/local/dailybk #空白不可注意

# MySQLrootパスワード
ROOTPASS=hogehgoerootpass

# backup(転送用)のユーザー
BACKUPUSR=backupusr

# バックアップ先ディレクトリ内前日ダンプ削除
find $BACKIDIR -type f -name "*.dump.sql.gz"|xargs rm -f

chmod -R 600 $BACKDIR
chmod -R 600 $DAILY_BACKDIR
touch $BACKDIR
touch $DAILY_BACKDIR

# データベース名取得
DBLIST=`ls -p /var/lib/mysql | grep / | tr -d /`

# データベースごとにバックアップ
for dbname in $DBLIST
do
    nice -n 19 mysqldump -uroot -p$ROOTPASS --default-character-set=binary $dbname | nice -n 19 gzip > $BACKDIR/$dbname.dump.sql.gz;
    chmod 600 $BACKDIR/$dbname.dump.sql.gz;
done

#dumpファイルを圧縮して保存、VPS側にも3日分保存する場合は下二行の方を有効に
nice -n 19 tar zcf $DAILY_BACKDIR/mysql_dailybk_mysqldump.tar.gz $BACKDIR
#find $DAILY_BACKDIR -type f -mtime +2 -name "mysql*dailybk*.gz" |xargs rm -f
#nice -n 19 tar zcf $DAILY_BACKDIR/mysql_dailybk_mysqldump_$NOW.tar.gz $BACKDIR

chmod 600 $DAILY_BACKDIR/mysql*dailybk*.gz
chown  $BACKUPUSR:users $DAILY_BACKDIR/mysql*dailybk*.gz
chown -R $BACKUPUSR:users $BACKDIR
chown -R $BACKUPUSR:users $DAILY_BACKDIR

ROOTPASS(mysqlのrootユーザーのパスワード)、BACKUPUSRは適宜変更してください。
mysqlのDB内からDB名を取得せず/var/lib/mysqlディレクトリからDB名を取得してるので乱暴ですが動くのでOK)

ここでは

  1. mysqldumpをぐるぐる回しながらデータベース毎にダンプファイルを作成して
  2. /backup/local/mysql/以下に最新ダンプをDB毎に保存して
  3. ついでに$DAILY_BACKDIR/mysql_dailybk_mysqldump.tar.gz に固めて保存し
  4. 読み取り権限と所有者の変更をしています。

また次回バッチ起動時に前回バッチファイルのダンプは/backup/local/mysql/をまるごとrm -rf しています。


スクリプト4:VPSサーバー側、darのディスクアーカイブ生成スクリプト

/root/bin/dar_backup.php (要chmod 700)

#!/usr/bin/php -q
<?php
if (!isset($argv[1])){
  echo "引数がありません -f or -d \n";
  exit;
}
$ptn = $argv[1];
//backupファイルの自VPS内設置場所
define("log_path", "/backup/local/darbk.log");
define("bk_base", "/backup/local/dar_file");

/**
 * #VPSサーバー側からのrsyncは行わず、ローカル側からのrsyncに起動方法を変更したため廃止
 * define("sync_use",false) ;
 * define("sync_to","remote-back@example.com:/backup/remote/mysvname/") ;
 */
define("nowtime", time());
// dar の起動オプション、-Z以下は負荷削減のため二重に圧縮しないファイル。:http://dar.linux.free.fr/doc/man/dar.html
$option = <<<___END___
-v
-D
-m 256
-y9
-Z "*.gz"
-Z "*.Z"
-Z "*.bz2"
-Z "*.tgz"
-Z "*.lzh"
-Z "*.zip"
-Z "*.jpg"
-Z "*.JPG"
-Z "*.jpeg"
-Z "*.JPEG"
-Z "*.png"
-Z "*.PNG"
-Z "*.gif"
-Z "*.GIF"
-Z "*.mpg"
-Z "*.MPG"
-Z "*.mpeg"
-Z "*.MPEG"
-Z "*.mp3"
-Z "*.rm"
-Z "*.jar"
-Z "*.exe"
-Z "*.EXE"
-Z "*.msi"
-Z "*.MSI"
-Z "*.divx"
-Z "*.flv"
-Z "*.mov"
-Z "*.wma"
-Z "*.iso"
-Z "*.img"
-Z "*.ISO"
-Z "*.IMG"
-Z "*.swf"
-Z "*.slc"
-Z "*.ts"
-Z "*.class"
___END___;

$option = preg_replace("/\r/iu", " ", $option);
$option = preg_replace("/\n/iu", " ", $option);
define("option_param", $option);
// バックアップ対象のディレクトリ(/直下のディレクトリ名のみ指定可能)、除外ディレクトリは"p"=>array("dir1","dir2",… ),で指定。
$bk_arr = array(
  // array("t"=>"backup"),
  array("t" => "bin"),
  array("t" => "boot"),
  array("t" => "dev"),
  array("t" => "etc"),
  array("t" => "home"),
  array("t" => "lib"),
  array("t" => "lost+found"),
  array("t" => "media"),
  array("t" => "misc"),
  array("t" => "mnt"), 
  // array("t"=>"net"),
  array("t" => "opt"), 
  // array("t"=>"proc"),
  array("t" => "root"),
  array("t" => "sbin"),
  array("t" => "selinux"),
  array("t" => "srv"), 
  // array("t"=>"sys"),
  array("t" => "tmp"),
  array("t" => "usr", "p" => array("local/delegate")), // ここでは/usr/local/delegeteは除外している
  array("t" => "var", "p" => array("cache", "spool/squid", "log", "named/chroot/proc")), // ここでは /var/cache, /var/spool/squid, /var/log等を除外している。ディスク空き容量の関係から/var/logはrsyncしてるので除外
  );

if ("-f" === $ptn || "-d" === $ptn){
  $msg = date("Y-m-d H:i:s", nowtime) . " Backup START\n";
  echo $msg ;

  switch ($ptn){
    case "-f":// 月次用フルバックアップ
      $pre_cmd = get_cmd_list("full", null, true);
      $cmd_list = get_cmd_list("full", $bk_arr);
      break;
    case "-d":// 日次用差分バックアップ
      $pre_cmd = get_cmd_list("diff", null, true);
      $cmd_list = get_cmd_list("diff", $bk_arr);
      break;
    default:
      $cmd_list = array();
      break;
  }
  foreach($pre_cmd as $k => $v){
    echo $v . "\n";
    system($v); //32日以上昔のdarファイルの削除
  }

  foreach($cmd_list as $k => $v){
    echo $v . "\n";
    system($v); //darバックアップの順次実行
  }
  $msg = date("Y-m-d H:i:s", time()) . " Backup END \n";
  echo $msg ;
}

/**
 * #VPSサーバー側からのrsyncは行わず、ローカル側からのrsyncに起動方法を変更したため廃止
 * if(sync_use){
 * if("-f"===$ptn||"-d"===$ptn||"-r"===$ptn){
 * $msg =  date("Y-m-d H:i:s",time()). " Rsync START \n";
 * echo $msg ;
 * $cmd = "rsync -auvz -e ssh --delete ".bk_base." ".sync_to." " ;
 * system($cmd);
 * $msg =  date("Y-m-d H:i:s",time()). " Rsync END \n";
 * echo $msg ;
 * }
 * }
 */
// 一連のコマンドの動的生成
function get_cmd_list($ptn, $bk_arr, $pre_flg = false)
{
  $cmd_arr = array();
  $diffstr = "";

  if ($pre_flg){ // 32日以上昔のdarファイルの削除コマンド生成
    $cmd = "for i in `/usr/bin/find " . bk_base . ' -name "*full*.dar" -mtime +32 ` ; do /bin/rm -f $i ; done ; ' ;
    $cmd_arr[] = $cmd;
    $cmd = "for i in `/usr/bin/find " . bk_base . ' -name "*diff*.dar" -mtime +32 ` ; do /bin/rm -f $i ; done ; ' ;
    $cmd_arr[] = $cmd;
  }else{ // darバックアップの順次実行コマンド生成
    foreach($bk_arr as $k => $v){
      $dname = $v["t"];
      $fullpath_h = bk_base . "/" . $dname . "-full-";
      $bkpath_h = bk_base . "/" . $dname . "-" . $ptn . "-";
      $bkpath = $bkpath_h . date("YmdHi");
      $tgt = " -R /{$dname} ";
      $no_tgt = "";
      if ("diff" === $ptn){
        $cmd = "/bin/ls -tr " . $fullpath_h . '*.dar|tail -n1 ' ;
        echo $cmd . "\n" ;
        $lsf = system($cmd);
        if (!$lsf){ 
          // echo " -----basefile none_a\n" ;
          continue;
        }
        $lsf = preg_replace("/^(.+)\.\d+\.dar$/iu", "\\1", $lsf);

        $diffstr = " -A " . $lsf ;
      }

      if (isset($v["p"])){
        foreach($v["p"] as $k2 => $v2){
          $no_tgt .= " -P " . $v2 ;
        }
      }
      $tgt .= $no_tgt ;
      $cmd_arr[] = " nice -n 10 dar " . option_param . " -c {$bkpath} {$tgt} {$diffstr} ";
    }
  }

  return $cmd_arr;
}
exit;

?>

$option のオプションパラメータはhttp://dar.linux.free.fr/doc/man/dar.html や man darや他の方のdar解説サイトをご確認ください。

  • y9は圧縮レベル
  • Zで圧縮不要ファイルを圧縮対象にしない。

みたいなのを1行毎に記述します。

$bk_arr の配列部分でバックアップ対象の/直下のディレクトリ名と、バックアップ除外対象のディレクトリ名を指定します。
例えば

array("t"=>"usr","p"=>array("local/delegate")),

の部分では/usrをバックアップ対象に含めるが、/usr/local/delegeteはバックアップ対象から除外しています。

get_cmd_list()
の部分ではdarファイルの作成コマンドと、32日以上昔のdarファイルの削除コマンドを生成しています。

生成するようにしています。

32日以上昔のdarファイルを削除すると、6/1にフルバックアップを取った後の 5/28の差分バックアップとかははっきり言ってゴミですがこのあたりは好きに調整してください。以前はVPS内部側保存期間も96日にしていたのですがディスク容量の関係で断念しました。(VPS側内部では31日保持し、手元に転送するときに工夫して手元では3ヶ月残す構成にするのも良いかと思います。)

中でゴニョゴニョなんかいじくり回していますがここで$optionと$bk_arrのパラメータから

 nice -n 10 dar -v -D -m 256 -y9 -Z "*.gz" -Z "*.Z" -Z "*.bz2" -Z "*.tgz" -Z "*.lzh" -Z "*.zip" -Z "*.jpg" -Z "*.JPG" -Z "*.jpeg" -Z "*.JPEG" -Z "*.png" -Z "*.PNG" -Z "*.gif" -Z "*.GIF" -Z "*.mpg" -Z "*.MPG" -Z "*.mpeg" -Z "*.MPEG" -Z "*.mp3" -Z "*.rm" -Z "*.jar" -Z "*.exe" -Z "*.EXE" -Z "*.msi" -Z "*.MSI" -Z "*.divx" -Z "*.flv" -Z "*.mov" -Z "*.wma" -Z "*.iso" -Z "*.img" -Z "*.ISO" -Z "*.IMG" -Z "*.swf" -Z "*.slc" -Z "*.class" -c /backup/local/dar_file/var-diff-201206230037  -R /var  -P cache -P spool/squid -P log -P named/chroot/proc  -A /backup/local/dar_file/var-full-201206010340

の様な呪文を作っています。科学となんとかかんとかで物語は始まる(とある科学のインなんとかさん


上記のスクリプトを動かしてみる

動かすとこんな感じでずらずらとバックアップファイルが生成されます。
mysqlのバックアップはディスク容量節約のために最新分のみが保存されています。
(手元側鯖への転送時に日付ごとディレクトリを作成してファイルを保管しています。)


/backup以下のtree

ディスク容量15GBのVPSで/backup以下は1.6GB位です。
(日次差分量によって変わってきます。これは殆ど更新の無いサーバー)



続きは中編:手元(ローカル)鯖側編へ

(勘の良い人はこれだけでなんとかなると思います。後編は実際に動かしてrsyncするお話&バックアップからの復元例のお話です。)


ではでは!

VPSで遊ぶ -その10 :軽量マイナーWebサーバー Cherokee Web Server であそぶ Part1:スクショ編

皆さんこんにちは!

最近ApacheでもnginxでもないCherokee Web Server ( http://www.cherokee-project.com )っていうWebサーバーをいじって遊んでいます。

VPS鯖が遅いので何か軽いWeb鯖が無いか探してる時に発見し、

  1. Webだけで各種設定が完結できる機能
  2. ワンクリックでCMSがインストール出来る独自機能

が面白そうなので試してみました。多分nginxとかの影に隠れて透明化してたんだろう。

これから数回に渡ってApacheの代替としてVPS鯖で使いこなせるか研究していきます。

I.スクショと紹介動画などなど

今日はスクショとコメントのみ。(LAN環境内のUbuntu10.04LTS & Ubuntu11.10で動作確認しました。)

まずは公式の動画から、

1)Webだけで各種設定が完結できる機能はこんな感じ。マスコットキャラがかわいい:

Cherokee Web Server - Introduction
ZendServer( http://www.zend.com/products/server/ )やColdFusion( http://www.adobe.com/jp/products/coldfusion-family.html ) の管理画面( http://d.hatena.ne.jp/dix3/20090222/1235252687 )みたいにWeb版管理画面で全部完結できます。


2)ワンクリックでCMSがインストール出来る独自機能はこんな感じ:

Cherokee Market: Installation and Drupal 7

実際に試しましたが色んなCMSを2-3分でインストール出来ます。オラビックリだ。


II.実際にインスコしたのでスクショなどなど

雰囲気良さげな感じ。物事全て表っ面から入る私向きです。

牧歌的なキャラが独特の雰囲気を醸し出してます:



フロント画面はこんな感じ(Cherokee Web Server 1.2.101):
サーバーの停止、CPU使用率、メモリー使用量等々。



スクショは英語で撮ってますが、こんな風に日本語化もされているみたいです。頭が下がります。:



Cherokee Market:
このWebサーバーなぜかCMSをWeb画面だけでインストール出来るんだぜ…ゴクリ



CMSやWebシステムが色々選択できます:

無料のものと、Pro版の様な有料のものがあります。
http://cherokee-market.com を見ると

  1. Drupal 6 , Drupal 6 Pro , Drupal 6 Enterprise
  2. Drupal 7 , Drupal 7 Pro , Drupal 7 Enterprise
  3. Joomla
  4. Liferay CE
  5. Magnolia CMS , Magnolia CMS Pro
  6. MediaWiki
  7. MoinMoin
  8. Moodle
  9. Nagios
  10. Nuxeo DAM
  11. Nuxeo DM
  12. OpenX
  13. phpBB
  14. phpMyAdmin , phpMyAdmin Pro
  15. Redmine
  16. SilverStripe CMS
  17. StatusNet
  18. Sugar CRM
  19. Trac
  20. Vanilla
  21. WordPress , WordPress Pro , WordPress Enterprise
  22. WsgiDAV
  23. Zen Cart

が今のところCherokee Marketに対応しているみたいです。
すぐにCMSの感触を試せるのはモノグサな私にはピッタリです。
マイクロソフト謹製WebMatrix ( http://www.microsoft.com/web/webmatrix/ )の*nix版みたいな。
Concrete5も入れてくれないかな…



Drupal7をインストール中の図:

画面のウィザードに沿って必要項目を入力していけばインスコ完了。
(依存関係でエラーが有る時にはその旨の指示がでて中断します。細かい挙動の説明は次回以降に)



Drupal7インストール完了の図:


今回はMySQLではなくSQLite3でインストールしてみました。Drupalもなんかスゲー進化してますねぇ…



その他管理画面等々(VirtualHost管理画面):
こんな感じでバーチャルホストもON・OFF・個別の設定など全部一元管理できます。素晴らしい。



バーチャルホストのトラフィックモニター:



バーチャルホスト毎の詳細設定画面(Behaviorタブ):

まだ調査中ですがキャッシュ制御だのアクセス制御だの色々Web画面だけで設定出来るみたいです。



その他管理画面等々(一般設定…ネットワーク設定等):

ポート設定:

実行ユーザー設定:

icon設定:

MIMEタイプ設定:


III.第一印象

たいへん気に入りました。Cherokeeいいじゃん。
CMSをイントラ内で素の状態で使う用途等では、Cherokee Market経由でサクっと環境を作り上げるのもいいんじゃね?あとCMSの研究用とかに。


次回はVPSサーバーへのインストールを想定して、
UbuntuServer10.04LTS (Lucid)32bitと、UbuntuServer 11.10 (Oneiric Ocelot) 32bit
仮想マシンに最新のCherokee Web Server 1.2.101を インストールする方法を記述します。


ではでは! (多分つづくしばしお待ちを…)

参考
Cherokee Web Server :
http://www.cherokee-project.com

ウィキペディアの解説 :
http://en.wikipedia.org/wiki/Cherokee_%28web_server%29

ドキュメント :
http://www.cherokee-project.com/doc/

Cherokee Market :
http://cherokee-market.com/

Hosting Websites with Cherokee – Linode Library :
http://library.linode.com/web-servers/cherokee

FuelPHP動作実験 - oil console & PHP Interactive改を使って マニュアルの例文コピペでいろんなメソッドを試してみよう☆彡

皆様こんにちは! FuelPHP Advent Calendar 2011 17日目です。

昨日は @madmamor さんの 「FuelPHPのcoreクラスを拡張してみる。 」でした。

本日のお題は『oil console & PHP Interactive改を使って マニュアルの例文コピペでいろんなメソッドを試してみよう☆彡』です。

  1. oil console を使ってコマンドラインFuelPHPのメソッドを呼び出してみる。
  2. PHP InteractiveからFuelPHPのメソッドを呼べるように微改造して、マニュアル例文コピペで色んなメソッドを試してみよう。

の開発・学習補助ネタ2本立てでいきます。

(ドキュメントの http://docs.fuelphp.com/packages/oil/console.html が今回該当する箇所になります。)

1. oil consoleって何よ?

FuelPHPのマニュアルを読んでいる最中、ちょっとだけコードの断片を試したい時、何かのコントローラーファイルに

<?php
(上略)
public function action_hogehoge()
{
  $arr1= Arr::average(array('1', 2, 4, '8'));
  var_dump($arr1);

  $arr2 = array('foo', 'bar', 'baz', 'yay');
  Debug::dump(Arr::to_assoc($arr2));

}
(下略)
?>

(Arr Class のお勉強中 - http://docs.fuelphp.com/classes/arr.html

みたいにソースをわざわざ書いてブラウザやIDEで確認するのってカッタルイですよね。
FuelPHPにはそういう時用のconsole機能があり、コマンドラインから色々試すことができます。 ( http://docs.fuelphp.com/packages/oil/console.html)
oilコマンドはソースのREADME.md,fuel,docs,public等と同じ階層にあります。:

    • -

php標準のdate()やdate_sunrise()なんてのを試してみたり、FuelPHPのArr::average()、Arr::to_assoc()を試してる図:

FuelPHPを最初から呼び出せるphp -a みたいなものですね。
ただ、

<?php
  $arr1= Arr::average(array('1', 2, 4, '8'));
  var_dump($arr1);
  $arr2 = array('foo', 'bar', 'baz', 'yay');
  Debug::dump(Arr::to_assoc($arr2));
?>

の様な一行コードをマニュアルからコピペして試したい時にはまあ良いんですが、

<?php
$people = array(
  array(
    "name" => "Jack",
    "age" => 21
  ),
  array(
    "name" => "Jill",
    "age" => 23
  )
);
print_r( Arr::assoc_to_keyval($people, 'name', 'age') );
?>

みたいにコードに改行が入ってしまった場合うまく動きませんorz
うわぁあぁヽ(`Д´)ノキモイヨー :


2. PHP InteractiveでFuelPHPのクラスを呼び出せるようにすればいいんじゃね?

で、このoilコマンドのconsole機能ですが一行毎にreadline()で入力を読み込んでevalしてるわけで複数行対応させるのは面倒。

そこで PHP Interactive - http://www.hping.org/phpinteractive/ の出番です。奥さん。
こいつは改行OKで複数行入力しながら、コードの断片をインタラクティブに動かして試せるスグレモノです。
こんな感じ:

で、PHP InteractiveにFuelPHPのbootstrap.phpを読みこませればいいんじゃね?というわけで微改造してみました。以下その記録。

注意:これはフォーム内に書いたコードをevalして実行してるwebプログラムなので決して公開サーバーに設置しないでください。スパーハカーの餌食、遠隔リモコン君になってしまいます。あくまでも勉強用の外部から繋がらないローカル環境のみで実験してください。

PHP Interactive改でお勉強してるイメージ図:

高級なコーヒーでも飲みながら軽やかにサンプルコードをドラッグ・アンド・ドロップしてupdateボタン押して動作確認。
んで、コードが動いたらその場で少しずつオプションや値を変えたりしながら理解を深めていきます。なんてインテリジェンスなんでしょう!…

では微改造作業に着手しましょう。

以下ローカル環境。外からつながる鯖に決して設置しないこと。スパーハカーの餌食(以下略



作業1:PHP Interactiveのソースをダウンロードする。
ソースは ttp://www.hping.org/phpinteractive/phpinteractive-0.2.tar.gz より取得してください。(サイトは http://www.hping.org/phpinteractive/

作業2:ソースを解凍してpublic/phpinteractive以下に設置、パーミッションもchown,chmodで正しくしておく。

(scripts以下は履歴保存ディレクトリなのでapacheユーザーの書き込み権限必須)


作業3:public/phpinteractive以下に.htaccessを新設
ソース: public/phpinteractive/.htaccess

php_flag short_open_tag On

phpinteractiveのソースがショートオープンタグを使っているのでこのディレクトリ配下のみshort_open_tagを有効にしています。
(サブディレクトリで.htaccessが使えない場合はapacheのconfファイルで AllowOverride Allになっているか要確認)


作業4:public/phpinteractive/includefuel.phpを新設
ソース: public/phpinteractive/includefuel.php

<?php
/**
 * Set error reporting and display errors settings.  You will want to change these when in production.
 */
error_reporting(-1);
ini_set('display_errors', 0);
/**
 * Website document root
 */
define('DOCROOT', dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR);

/**
 * Path to the application directory.
 */
define('APPPATH', realpath(dirname(dirname(__DIR__)).'/fuel/app/').DIRECTORY_SEPARATOR);

/**
 * Path to the default packages directory.
 */
define('PKGPATH', realpath(dirname(dirname(__DIR__)).'/fuel/packages/').DIRECTORY_SEPARATOR);

/**
 * The path to the framework core.
 */
define('COREPATH', realpath(dirname(dirname(__DIR__)).'/fuel/core/').DIRECTORY_SEPARATOR);

// Get the start time and memory for use later
defined('FUEL_START_TIME') or define('FUEL_START_TIME', microtime(true));
defined('FUEL_START_MEM') or define('FUEL_START_MEM', memory_get_usage());

// bypass shutdown_handler
define('MY_INTERACTIVE_MODE', true);

// Boot the app
require APPPATH.'bootstrap.php';

//FUEL_ENV check
if('development'!==\Fuel::$env)
{
  echo 'development mode is required.';
  exit;
}
?>

oilを参考に作りました。MY_INTERACTIVE_MODE の定数は独自に追加しています。
うっかり本番の公開サーバーにアップロードしてしまった時の気休めのため\Fuel::$envがdevelopment以外の時は何もせずここで終了させています。(参考 http://docs.fuelphp.com/installation/instructions.html#/setting_the_environment )


作業5:public/phpinteractive/index.phpを微修正
修正箇所は以下のとおりです。
変更箇所の画面ショット:

オリジナルとの差分:

root@star:/var/vhosts/contact.pizw.net/public/phpinteractive# diff index.php orig_index.php
2d1
< include('./includefuel.php');
94c93
<       if(!@unlink("scripts/$name"))
---
>       if(!@unlink("scripts/$name"))
135c134
<               if (rename_script($current, $newname) == 0)
---
>               if (rename_script($current, $newname) == 0)
441,442c440
<    //echo(htmlentities($code))
<    echo(htmlentities($code, ENT_QUOTES, mb_internal_encoding()));
---
>    echo(htmlentities($code))
498,499c496
<       //$script_output = "<pre>".htmlentities($script_output)."</pre>";
<       $script_output = "<pre>".htmlentities($script_output, ENT_QUOTES, mb_internal_encoding())."</pre>";
---
>       $script_output = "<pre>".htmlentities($script_output)."</pre>";

2行目では新設したincludefuel.phpをincludeしています。これでFuelPHPのクラスが呼べるようになります。
441,498行目付近は日本語文字列を化けないようにする目的での修正です。


作業6:fuel/app/bootstrap.phpでError::のクラスを拡張出来るように変更
ソース: fuel/app/bootstrap.php

<?php

// Load in the Autoloader
require COREPATH.'classes'.DIRECTORY_SEPARATOR.'autoloader.php';
class_alias('Fuel\\Core\\Autoloader', 'Autoloader');

// Bootstrap the framework DO NOT edit this
require COREPATH.'bootstrap.php';


Autoloader::add_classes(array(
	// Add classes you want to override here
	// Example: 'View' => APPPATH.'classes/view.php',
	'Error' => APPPATH.'classes/error.php',//←この行を追加
));

// Register the autoloader
Autoloader::register();

/**
 * Your environment.  Can be set to any of the following:
 *
 * Fuel::DEVELOPMENT
 * Fuel::TEST
 * Fuel::STAGE
 * Fuel::PRODUCTION
 */
Fuel::$env = (isset($_SERVER['FUEL_ENV']) ? $_SERVER['FUEL_ENV'] : Fuel::DEVELOPMENT);

// Initialize the framework with the config file.
Fuel::init('config.php');

?>

PHP Interactiveの入力側でPHPの文法エラーになるような書き方をした場合、fuel/core/classes/error.phpのshutdown_handler()が反応し(fuel/core/bootstrap.php内のregister_shutdown_functionを参照)そこでexitしてしまうのですが、これではPHP Interactiveの画面も表示されなくなり、次の操作に困るため\Error::shutdown_handler()を微変更します。

こんな感じで止まると続行できなくて困るでよ:


作業7:fuel/app/classes/error.phpを新設
コアのクラスファイルを拡張します。(ドキュメント: http://docs.fuelphp.com/general/extending_core.html)
ソース: fuel/app/classes/error.php

<?php
/**
 * Core Class Extends Example (error.php)
 * @package
 * @version 0.01
 * @author mataga
 * @license MIT License
 * @copyright 2011 mataga
 * @link http://twitter.com/mataga
 */
class Error extends Fuel\Core\Error
{

  /**
   * Native PHP shutdown handler
   *
   * @return  string
   */
  public static function shutdown_handler()
  {
    $last_error = error_get_last();

    // Only show valid fatal errors
    if ($last_error AND in_array($last_error['type'], static::$fatal_levels))
    {
      $severity = static::$levels[$last_error['type']];
      logger(Fuel::L_ERROR, $severity.' - '.$last_error['message'].' in '.$last_error['file'].' on line '.$last_error['line']);

      $error = new \ErrorException($last_error['message'], $last_error['type'], 0, $last_error['file'], $last_error['line']);

      //MY_INTERACTIVDE_MODE -- ADD BEGIN
      if(defined('MY_INTERACTIVE_MODE') && MY_INTERACTIVE_MODE)
      {
        echo \Debug::dump($last_error);
        return;
      }
      //MY_INTERACTIVDE_MODE -- ADD END

      if (\Fuel::$env != Fuel::PRODUCTION)
      {
        static::show_php_error($error);
      }
      else
      {
        static::show_production_error($error);
      }

      exit(1);
    }
  }

}//endofclass
/* End of file error.php */
?>

//MY_INTERACTIVDE_MODE で挟んだ5行を追加しただけです。これでパースエラーが起きても PHP Interactive改経由の場合にはこんな感じで処理は続行されます。


作業8:動作確認
ttp://example.com/phpinteractive/ を開いていろいろ試すべし
こんな感じでArr::の実験をしてみたりすると、すぐに細かい動きを確認できて使い方を覚えるわけです。



というわけでFuelPHPのドキュメントサイト( http://docs.fuelphp.com/ )のサンプルコードを動かしまくって、あなたも燃料テイスターになりませんか?

触れば触るほど深い味わいがしますぜ。



明日は @NEKOGET さんの「FuelPHPの呼び方。」になります。

ではでは!

FuelPHP動作実験 - 実験くんソースをModulesに閉じ込めてモジュール分割してみる。

皆様こんにちは! FuelPHP Advent Calendar 2011 12日目 は私 @mataga が担当します。
昨日は @madmamor さんの 「FuelPHPでFacebookアプリを作ってみよう。実装編。 」でした。

本日のお題は『実験くんソースをModulesに閉じ込めモジュール分割してみる。』です。

  • FuelPHPのソース群を機能毎にモジュール分割してみる。
  • HMVC機構のおかげでコントローラAの中で別モジュールのコントローラーBが呼び出せるでよ。

のサワリ部分を体験をしていきます。
(ドキュメントの http://docs.fuelphp.com/general/modules.html , http://docs.fuelphp.com/general/hmvc.html が今回該当する箇所になります。)


※1 モジュール分割(Modules内へのディレクトリ構成移動)を行わなくても、
http://docs.fuelphp.com/general/controllers/base.html#/controller_in_subdir で解説されている様に

なんて芸当は出来るわけですが、私はModulesを愛してやまないのでここでは無視します。


I:HMVCってなんぞや?名前だけ無駄にカッコイイんだけど?

の詳細ついてはHMVC…"Hierarchical Model View Controller"でググってもらうとして(丸投げ)、

日本語超俺訳で言うと「機能毎にMVC構造をモジュール分割(フォルダ分け)して階層化してしまえ+モジュールの中から別のモジュールを呼べるようにしてしまえ。」って事です。
ソースのモジュール化を推し進めることでHMVC…"Hierarchical Model View Controller"をガシガシ使えまっせ。
※1の通りコントローラーの階層化なら別にモジュール分割しなくても実現出来るといえば出来るのですが、折角なら美しくモジュール分割したいじゃなイカ。そこの所は深く突っ込まないでください。(一部の機能だけモジュール化することも可能です。)

"ブロック構造で機能を切り出したツギハギ可能なCMSを作る場合に、どのようなディレクトリ構成にすればうまく分業して作業を進められるか?"…を想像してみるとわかりやすいかなと思います。建築で言うと2x4工法みたいな。机の中をビシっと間仕切りしたい性格な私にピッタリ!

モジュール化した時のディレクトリ構造の違い:

考えるより感じろって事でtreeコマンドの画面ダンプをペタリ。(以後画像A

(画像Aクリックで拡大可)
左側はサンプルソースstationwagon ( https://github.com/abdelm/stationwagon 、関連記事 http://d.hatena.ne.jp/dix3/20111119/1321684421)のソース群に、FuelPHP標準添付のapp/classes/controller/welcome.php(コントローラ)とapp/views/welcome/index.php(ビュー)を混ぜたもの。
右側はstationwagon由来のソースを app/modules(モジュール用ディレクトリ)/sbox(モジュール名:俺命名/以下に移動し、FuelPHP標準添付のwelcome画面をapp/modules/welcome/以下に移動したもの。

分割の仕方をここでは簡単のためソースの由来で分けていますが、実戦では機能で分ける事 (adminモジュール・cartモジュール・blogモジュール・usersモジュール・widgetモジュールetc)を想像すればモジュール化するメリットが想像しやすいかと。(cart,widgetモジュールはblogモジュール内から呼び出せる複雑な構成)


II:Modules化作業手順

というわけでモジュール化の素晴らしさをご理解頂けたとして、作業手順を書いていきます。
丁度実験君環境 stationwagonのソースが公開されているので( https://github.com/abdelm/stationwagon )これらのソースをModulesに移動する手順を追いながら動作実験してみましょう。

出来ればこちらの記事 等を参考に予めstationwagon環境を準備してひと通り触られた後の方が分かり良いと思います。

手順1:app/modules/sbox(任意のモジュール名)/以下に色んなディレクトリを作成する


root@star:/var/vhosts/sandbox/stationwagon/fuel/app/modules# tree -d
.
└── sbox(モジュール名:好きに命名
├── classes(必須)
│   ├── controller(コントローラー用ディレクトリ)
│   ├── model(モデル用ディレクトリ)
│   └── view(optional:ViewModels用ディレクトリ:今回使わないので無くても良い。というかモジュール化したあとViewModelsは使えなかった。要継続調査)
├── config(設定ファイル用ディレクトリ)
├── lang(言語ファイル用ディレクトリ)
└── views(ビューファイル用ディレクトリ)
├── articles (views内のサブディレクトリ。今回使用ソースstationwagonのviews構成でこうなっている為)
├── categories(上に同じ)
└── users(上に同じ)
ポイント:

  1. 上記の緑・赤・青の太字色つき部分のディレクトリ構成が基本ですが、使用しないディレクトリは不要です。(langやconfigディレクトリ等)
  2. モジュール名は解説用にsboxと名前をつけました。この場合URLはttp://example.com/sbox/hogehoge(アクション名) の様になります。(Routingを弄った場合はこの限りではない。)

また、今回はstationwagonのソース群をsboxという1モジュールにまるごと閉じ込めましたが、

  • articles機能をmodules/mod1(任意の名前)モジュール配下に
  • users機能をmodules/mod2モジュール配下に
  • categoriesをmodules/mod3モジュール配下に

という分割の仕方も大いにアリです(というかそっちが本流か)。

手順2:app/modules/sbox(任意のモジュール名)/以下にソースファイルを移動する

移動前・移動後のファイル構成の差異は上述した画像Aの通りです。移動後はこんな感じ

root@star:/var/vhosts/sandbox/stationwagon/fuel/app/modules/sbox# tree
.
├── classes
│   ├── controller
│   │   ├── articles.php
│   │   ├── categories.php
│   │   ├── common.php
│   │   └── users.php
│   ├── model
│   │   ├── article.php
│   │   └── category.php
│   └── view
├── config
├── lang
└── views
├── 404.php
├── articles
│   ├── add.php
│   ├── edit.php
│   └── index.php
├── categories
│   ├── add.php
│   ├── edit.php
│   └── index.php
├── template.php
└── users
├── index.php
├── login.php
└── signup.php
特にコメント無し。とりあえず移動すればヨロシ。
(404ページのビューや、ビューの外枠テンプレートのtemplate.phpはモジュール外がいいとかそのへんの細かい調整はあとで考えましょう。)

手順3:app/config/config.php 内でmodule_pathsの指定をおこなう

'module_paths' => array(
        APPPATH.'modules'.DS
),

160-180行目付近にあります。コメントアウト//を外して有効にすることでapp/modules/ がモジュール用ディレクトリとして使えるようになります。
(ドキュメントの http://docs.fuelphp.com/general/modules.html#/module_config

手順4:移動したソース内のソースを一部書き換えていく。

ディレクトリを移動したことで各ソース内の一部書き換えが必要となります。
(ドキュメントの http://docs.fuelphp.com/general/modules.html#/module_namespace , http://docs.fuelphp.com/general/modules.html#/static_calls
基本単純作業ですがnamespaceの絡みもあり、CodeIgniterのモジュール化(ファイルを移動すればほぼ作業完了)の時と多少勝手が違うのでちょいと悩みました。公式のドキュメント整備が進んでくるとよりよいチュートリアルが載ってくると思います。

(実際に書き換えた箇所はstationwagonのソースの数を見てもらえればわかる通り結構多いので、変更パターンのみ以下に記述します。)
ソース書き換えの例

実例A とあるコントローラー

root@star:/var/vhosts/sandbox/stationwagon# hg  diff -r 61a788776698 fuel/app/modules/sbox/classes/controller/common.php
diff -r 61a788776698 fuel/app/modules/sbox/classes/controller/common.php
--- a/fuel/app/modules/sbox/classes/controller/common.php       Tue Dec 06 18:30:18 2011 +0900
+++ b/fuel/app/modules/sbox/classes/controller/common.php       Sat Dec 10 19:26:44 2011 +0900
@@ -1,6 +1,6 @@
 <?php
-
-class Controller_Common extends Controller_Template {
+namespace Sbox;
+class Controller_Common extends \Controller_Template {

        public function before()
        {
@@ -16,20 +16,21 @@
         }

         // Check user access
-        $access = Auth::has_access(array(
+        $access = \Auth::has_access(array(
             $this->request->controller,
             $this->request->action
         ));

                if ($access != true)
         {
-            Response::redirect('users/login');
+
+           \Response::redirect('sbox/users/login');
         }
         else
         {
-            if (Auth::check())
+            if (\Auth::check())
                    {
-                       $this->user_id = Auth::instance()->get_user_id();
+                       $this->user_id = \Auth::instance()->get_user_id();
                        $this->user_id = $this->user_id[1];
                    }
         }
@@ -42,7 +43,7 @@

                // Set a HTTP 404 output header
                $this->response->status = 404;
-               $this->template->content = View::factory('404', $data);
+               $this->template->content = \View::factory('404', $data);
        }
 }

実例B とあるモデル

root@star:/var/vhosts/sandbox/stationwagon# hg  diff -r 61a788776698 fuel/app/modules/sbox/classes/model/article.php
diff -r 61a788776698 fuel/app/modules/sbox/classes/model/article.php
--- a/fuel/app/modules/sbox/classes/model/article.php   Tue Dec 06 18:30:18 2011 +0900
+++ b/fuel/app/modules/sbox/classes/model/article.php   Sat Dec 10 20:58:44 2011 +0900
@@ -1,16 +1,16 @@
 <?php
+namespace Sbox;
+class Model_Article extends \Orm\Model {

-class Model_Article extends Orm\Model {

(中略)

@@ -22,7 +22,7 @@

     public static function validate($factory)
     {
-        $val = Validation::factory($factory);
+        $val = \Validation::factory($factory);

         $val->add('category_id', 'Category');

(上記実例Aのstationwagonの共通コントローラーcommon.phpは、fuel/core/classes/controllerのController_Templateを継承したController_Commonというクラスで、このモジュール内の他のコントローラー(articles.phpなど)はcommon.phpを継承しています。common.phpにはコントローラー内各アクション(URL)呼び出し直前の共通処理before()や、404ページ表示用アクション action_404() が書かれています。 旧:factory()→新:forge()へのメソッド名書き換えは今回の主題からは外れるのでそのまま)
それでは要変更箇所を書いていきます。

変更1 : コントローラ・モデルファイルではモジュール名(=ディレクトリ名)の namespace を必ず追加する
コントローラでは、 namespace モジュール名;の宣言を行う。(ここでは namespace Sbox;)

参考: http://docs.fuelphp.com/general/modules.html#/module_namespace
名前空間命名規則 PSR-0云々についての深い議論については http://fuelphp.com/blog/2011/04/classnames-autoloading-namespaces 等もご参考ください、今回はFuelPHPの仕様についての私見は省略します。core/classes/autoloader.php のalias_to_namespace($class, $namespace = '')のclass_alias($class, $root_class);周りでglobal namespaceにエイリアスを作っているようなのでスーパーハカーはこの辺でも弄って上手いことやればお好みの形になるかと。

変更2 : コントローラ・モデル内でコアのクラスを呼び出しているところの頭に\を追加
ポイント1でモジュールのnamespaceを宣言したことに伴い、ソース内微調整を行ないます。
上記実例Aと実例Bでは

class Controller_Common extends Controller_Template {

class Controller_Common extends \Controller_Template {
class Model_Article extends Orm\Model {

class Model_Article extends \Orm\Model {
Auth::

\Auth::
Response::

\Response::
View::

\View::
Validation::

\Validation::
と至る所で\が付けられている事が解るかと思います。

また、今回のサンプルソースでは該当箇所は存在しませんがoilコマンド等で自動生成されたモデルの場合モデルのクラス名が

namespace Model;
class Contact extends Orm\Model {

の様にnamespace Model; 、class モデル名 extends…となっている場合があります。(というかこっちのほうがマニュアルに載っている書き方)(参考 http://docs.fuelphp.com/general/models.html
この場合には


//namespace Model;

namespace Sbox\Model;
//class Contact extends Orm\Model {

class Contact extends \Orm\Model {
という様な書き換えが必要となります。

尚、コントローラから呼び出された先のViewファイル内(例:sbox/views/articles/edit.php)などで
<?php echo Form::input( と記述されている箇所を\Form::input(にする必要はありません。

変更3 : コントローラ内やビューファイル内で\Response::redirect('飛び先');や、Html::anchor('飛び先');としている箇所の飛び先をモジュール名付きの適切なパスに書き換える
例えば categories/add だったリンク先は今回モジュールsboxの下にソース群を設置したので、sbox/categories/add への書き換えが必要となります。

変更4 : ユーザー認証にSimpleAuthを使っている場合のconfig/simpleauth.phpのroles設定書き換え
stationwagonのソースではユーザー認証にFuelPHP標準搭載の SimpleAuth - http://docs.fuelphp.com/packages/auth/simpleauth/intro.html (2011/12/10現在公式のマニュアルがまだイントロ部分しか無い)を使っているのですがここのroles設定にもモジュール名の追加が必要となります。

root@star:/var/vhosts/sandbox/stationwagon# diff  ../stationwagon-nohmvc/fuel/app/config/simpleauth.php  fuel/app/config/simpleauth.php
59c59
<             '\Controller_Users' => array(
---
>             '\Sbox\Controller_Users' => array(
64c64
<             '\Controller_Users'  => array(
---
>             '\Sbox\Controller_Users'  => array(
71c71
<             '\Controller_Users'  => array(
---
>             '\Sbox\Controller_Users'  => array(
74c74
<             '\Controller_Articles'   => array(
---
>             '\Sbox\Controller_Articles'   => array(
80c80
<             '\Controller_Categories' => array(
---
>             '\Sbox\Controller_Categories' => array(

…というわけで説明を書くとタイヘンなんですがやることは単純で、

  1. コントローラー、モデルには namespace モジュール名をつける。
  2. コントローラー、モデルのファイル内でfuel/core/classes/配下のコアのクラスを呼び出している部分には\を頭につける
  3. リダイレクト先やリンク先が変わるのでモジュール名追記分を考慮して訂正する。
  4. 認証周りなどの設定もモジュール名追記分を考慮して書き換える

という所がこの作業のキモとなります。


III:モジュール化済のwelcomeモジュールからsboxモジュールのlogin画面を呼び出してみる

ここまで書くのに疲れたので後は惰性の画面ショットで…

welcome/action_index (ttp://example.com/welcome/) にてsbox/users/login/ をモジュールとして呼び出しているソース部分




welcome画面にTumblrっぽいログインブロック(sbox/users/login/)が出現!




(普通のwelcome画面はこんな感じ)




(sboxモジュール単体でのsbox/users/login/画面はこんな感じ)


HMVCを使う雰囲気がなんとなくわかりますでしょうか?

(※このソースで実験用にはなんとなく別モジュールのコントローラーを呼び出して動かしているだけですので、実際の実装では呼び出される側・呼び出す側にちゃんとしたエラー処理&モジュール単体で呼ばれないようにする方策等が必要になるかと思います。)
stationwagonはFuelPHPの雰囲気を味わう練習ソースレベルなので実務レベルに持って行くにはもっと例外処理・実装の追加が必要です。あくまでも安全なローカル環境の下で試す砂場・実験場として考えてください。くどいですが再度書いておきます。


IV:今回モジュール化実験で使ったソース

改造前ソースについては、https://github.com/abdelm/stationwagon より取得してください。
改造後ソースについてはstationwagonのライセンスが明記されていない為公開は控えておきます。

一度書き換えて動くようになるまで試してみればFuelPHPの構造が色々見えてくるので、この機会にモジュール化にチャレンジしてみては如何でしょう?

(モジュール化作業途中でAuth::を\Auth::にし忘れて、そんなクラス見つからねーヨと怒られてる図)



モジュール化済みでのソース実装は現在 http://contact.pizw.net/http://contact.pizw.net/contact/srcwindow にてお問い合わせフォーム作成実験をゼロからジワジワ進行中です。そちらの方でなんとなく雰囲気を掴んでもらえればと思います。


明日は @ounziw さんの「PHP5.3 の名前空間入門」になります。よろしくおねがいします。

ではでは!

鉄は熱いうちに打て-コピペプログラマーのためのFuelPHP動作実験その1(サンプルソース&お砂場づくり)

みなさまお久しぶりです。今日は今流行り?のFuelPHP( http://fuelphp.com/ )について調べてみます。
ネーミングにスパイスのきいたフレームワークです。
発火装置よりも燃料とかなんのジョークかいジョニー?HAHAHA〜
(今流行りというか後2年位したら流行るんじゃなかろうか)

CodeIgniterだとライセンス云々が仕事上マンドクセとか、モダンフレームワーク弄りたいけど今更ヘビーなFWを勉強するのは嫌だとかそういう気分な時の救世主候補としてどうよ?

(OSLでも実質影響ないと思うので私はそれほど気にしてはいないです。個人的にはフルオープンすらOK。ただ厳密に調査しないといけない事情のある方には気のかかる要素ではあります。)


1.勉強するっていってもサンプル無いとメンドクセ(;´Д`)y─┛

時給250万の俺的にコマンドラインからソースをダウンロードして→テーブルをコマンドラインでつくって→ソースを書いて→その間に色々覚えて→ソース解析って儀式すら面倒くさい。(誰か雇ってください)

というわけで、手っ取り早くDB連携しながら動くサンプルソースが無いか検索してみました。

https://github.com/abdelm/stationwagon

Stationwagon is full of examples and is updated regularly with the latest Fuel changes.

There are a lot of examples on these classes: - Orm - Auth - Pagination - Validation - and more!


俺訳:
ステーションワゴンはサンプルコード集だにょ。
FuelPHPに新機能が追加されたらそいつも順次実装していくにょ。
ORM-認証-ページネーション-バリデーションとかのサンプルがイパーイあるでよ。

って事で勉強用のブログシステム風サンプルソースみたいです。(そのまま設置して公開!みたいな実用性はないので注意)
パッと見はこんな感じ:

こういうのはとりあえず動くソースを壊しながら勉強するのが一番近道&ものぐさな俺に最適。
基本の登録・更新・削除・ユーザー追加・ログイン・カテゴリー作成などの基本動作確認の1つのサンプルとしてはまあ良いのでは?(フレームワーク内のソースの関係を確認する初歩の初歩の手習い用途です。例外処理等がたいして入ってないのでそのまま公開して使う目的では決してないです。ご注意を。)

あとはドキュメントで見かけたサンプルコードをこいつにぺたぺた貼って実験していけばお砂場にはなるかと。
(このサンプルソースのコードの品質は知りません、勉強用サンドボックスを一から作るのを短縮する用途です。あしからず)

2.ダウンロード〜インストール

0.環境
名前空間使ってるのでPHP5.3以降じゃないと動かないアルネ
Ubuntu 11.04-Apache/2.2.17-PHP 5.3.5-MySQL5.1.54の一般的な開発環境です。それ以外はシラネ
あとmod_rewriteとかその辺は適当にやってくりゃれ。鯖弄るのでルート環境必須。手元の環境でやるべし

1.ダウンロード
git cloneとかスマートな方法じゃなくてもZipでクレでおk
https://github.com/abdelm/stationwagon/downloads

2.インストール
適当なディレクトリに展開

(ここでは/var/vhosts/stationwagon.pizw.com/stationwagon/ )

pizw.comは俺所有ドメインWindowsのhostsファイル(C:\Windows\System32\drivers\etc\hosts や、Linuxの/etc/hostsに192.168.xx.xx stationwagon.pizw.com 等々として名前解決させてます。

(ローカルネットワーク環境で繋がりさえすればstationwagon.example.comでも127.0.0.1でもなんでもいい。)

3.DB作成&テーブルインポート
database.sqlは展開したソース直下にあります。

# mysqladmin create stationwagon
# mysql -uroot stationwagon < database.sql
# mysql -uroot stationwagon
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3380
Server version: 5.1.54-1ubuntu4-log (Ubuntu)

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL v2 license

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show tables;
+------------------------+
| Tables_in_stationwagon |
+------------------------+
| sw_articles            |
| sw_categories          |
| sw_users               |
+------------------------+
3 rows in set (0.00 sec)

mysql>

なんかテーブルが3つできてますね。
mysqlのroot権限がいやならユーザー作って好きに汁。

4./etc/apache2/sites-available の設定
/etc/apache2/sites-available/stationwagon.pizw.com(任意の接続先バーチャルホスト鯖名) の作成

<VirtualHost *:80>
  ServerName stationwagon.pizw.com
  ServerAdmin webmaster@localhost
  DocumentRoot /var/vhosts/stationwagon.pizw.com/stationwagon/public/
  <Directory /var/vhosts/stationwagon.pizw.com/stationwagon/public/>
    Options -Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    allow from all

    RewriteEngine on
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

    RewriteRule ^(.*)$ index.php/$1 [L]
  </Directory>
  ErrorLog /var/log/apache2/stationwagon-error.log
  LogLevel warn
  CustomLog /var/log/apache2/stationwagon-access.log combined
</VirtualHost>

/var/vhosts/stationwagon.pizw.com/stationwagon のところは適当に書き換えてちょ

5./etc/apache2/sites-enabled の設定

# cd /etc/apache2/sites-enabled
# ln -s ../sites-available/stationwagon.pizw.com(さっき作ったファイル) ./
# /etc/init.d/apache2 graceful

さてこれでつながるようになった。

6.FuelPHPソース内の一部設定ファイル書き換え
一部設定の調整
fuel/app/config/development/db.php
'table_prefix' => 'sw_', を以下の様に追加。

<?php
/**
 * The development database settings.
 */

return array(

  'default' => array(
    'type'        => 'pdo',
    'connection'  => array(
      'dsn'        => 'mysql:host=localhost;dbname=stationwagon',
      'username'   => 'root',
      'password'   => '',
    ),
    'identifier'   => '`',
    'table_prefix' => 'sw_',
    'charset'      => 'utf8',
    'caching'      => false,
    'profiling'    => false,
  ),
);

?>

作られたテーブル名が sw_articles,sw_categories,sw_users だけどソースではsw_無しの(articles,categories,users)前提で書かれているので云々

ちなみに上手く動かないと、開発モードでは:

こんな美麗なdebug_backtrace画面が出る。

リリースモードではこんなエラー画面:

.htaccessに SetEnv FUEL_ENV production を追記するとリリースモードで動く親切設計。
このへんもCodeIgniterだとなんかオレオレハックしてた過去の記憶が…
あと、

  1. fuel/app/config/development/db.php
  2. fuel/app/config/db.php

と二種類同じような設定ファイルがあるけれど、開発モードだと1が存在すると2で仕込んだ標準設定がオーバライドされるようだ。地味だがなにげに嬉しい。( fuel/app/config/production/db.php が存在するとこちらを優先)


3.動作確認

CMS風のサインアップ・ログイン・登録・更新・削除・一覧機能・カテゴリ機能があるようです。(なぜか作成されたブログを外部から見る機能が無い気がするwまだ中身解析してないので詳細よくわからん)
サインアップ(users/signup)周りの動きはこの辺( https://github.com/abdelm/stationwagon/tree/develop/fuel/app/classes/controller/users.php )を崩せば解るか:

Auth::の挙動確認に使える?(users/login):


バリデーションやDBへのINSERT-UPDATE-DELETE周りはこの辺(https://github.com/abdelm/stationwagon/tree/develop/fuel/app/classes/controller/articles.php )か:

DBからのSELECTや一覧作成周りはこの辺(articles,categories) (https://github.com/abdelm/stationwagon/tree/develop/fuel/app/classes/controller/categories.php )か:


あとはドキュメント (http://docs.fuelphp.com/ )でも見ながらソースを解析して色々試しながら遊んで行きましょー。


4.ファーストインプレッション

ドキュメントとコアのソースを眺めつつ、雰囲気でモノを語ってますが、CodeIgniterで物足りないと感じてた部分が最初から補完されている感じです。
今後仕事でも使用できるかちびちびお試し実験していく予定です。

気に入ったのは

  1. 静的クラスメソッドの呼び出しがメイン($this->load->うんたらしなくてよい)、
  2. HMVC,CSRF,ORM,MongoDB,Auth,REST機能入り
  3. 名前空間使用で整理整頓好きに優しい
  4. vendor配下に外部ソース設置できそう(ここにZend Frameworkを突っ込んでキメラ化できるか?)
  5. Session方式をクッキー・ファイル・DB・memcached等から色々選べる
  6. 細かいけどvalidatorの書き方が直感的 例: $val->add('password', 'Your password')->add_rule('required')
  7. 新しいフレームワークは既存フレームワークよりソースの取り回しが賢くなっている。便利さがじわじわくる。


MVCフレームワークって突き詰めていくとどれも似たような感じに収束してくるけど、
後から出てくるものの魅力は
「標準でアレが欲しいよな→作るか」が、「最初から考慮された状態」で組み込まれてる事だと思う。
最初の頃から標準であるべきものの概念は着実に変化している。まさにドッグイヤーですわ(キリッ。


使い慣れたCodeIgniterは安牌でとても良いけれど、新しい燃料で刺激に燃えながら仕事するのもいいんじゃね?


ではでは!

参考:

FuelPHP:
http://fuelphp.com/

PHP フレームワークの「FuelPHP」がスゴすぎる - A Day in Serenity @ Kenji :
http://d.hatena.ne.jp/Kenji_s/20111117/1321519058