Perl版のHerokuを作りたい

発端

最近Salesforceに買われたのでちまたでも有名になったHeroku,RubyOnRailsをオンデマンドで使う事ができるサービスです.このサービスではgitで管理しているソースをHeroku環境にデプロイしてmakeするとwebアプリケーションを実行できます.似たようなことをPerlでもやってみたいなと思っていました.
一方で子飼さんが作ったlleval - run codes from your browserというサービスと,これを元にした宮川さんのhttp://sunaba.plackperl.org/app/jrl_h69c3xgmlha_byudvgというサービス(というかproof-of-conceptかな)が動いています.これらの巨人(子飼さんはともかく宮川さんはでかくないけど)の偉業を足がかりにすればできるんじゃないかと思いつきました.
そして,最近iTunes App StoreCodeToGoというアプリを見つけて,もしかしたらニーズもあるかもしれないと思うようになりました.ビジネスモデルは(苦手だから)後回しにして,とりあえず動きそうなアーキテクチャを考えてみることにしました.

構想

アーキテクチャに名前がないと説明しにくいのでHakoniwa(箱庭)という名前を付けてみました.まず,ローカル環境に必要なアプリケーションをインストールして,

$ cpanm App::Hakoniwa

例えば,bonsan(盆山)ってwebアプリを作る場合には

$ hakoniwa create bonsan

とします.するとHakoniwaがaws(amazon web service)上にbonsan.hakoniwa.com, bonsan.api.hakoniwa.comという名前のサーバ(AMIインスタンス)用意します.bonsan.hakoniwa.comにはsunaba(もしくはその拡張)が,bonsan.api.hakoniwa.comにはlleval(の移植版)が動きます.
次にデプロイするにはgitを使います.Hakoniwaが必要な環境変数(の初期値を)用意しておけばgitがそのまま使えるかなと.

$ git push hakoniwa master

アプリケーションを起動はこれでいいかな.

$ hakoniwa plackup bonsan

負荷分散は要求に応じてbonsan001, bonsan002...とアプリケーションサーバを増やして,それをsunabaに組み込んだproxy機能で分散させればいいと思っています.
hakoniwaのコマンドオプションはこれぐらいあればいいかと.

$ hakoniwa --help
help                 # show this usage
version              # show hakoniwa version
list                 # list your apps
create <name>        # create a new app
destroy <name>       # destroy the app permanently
info [<name>]        # show app info, like web url and git rep
api:add [<num>]      # add api server
api:del [<num>]      # delete api server
keys                 # show your user's public keys
keys:add [<path to keyfile>] # add a public key
keys:remove <keyname>        # remove a key by keyname
keys:clear                   # remove all keys
domains:add <domain>         # add a custom domain name
domains:remove <domain>      # remove a custom domain name
domains:clear                # remove all custom domains
ssl:add <pem> <key>          # add SSL cert to the app
ssl:remove <domain>          # removes SSL cert from the app domain
pm                           # list installed modules
pm:install <perl_module>     # install the module from CPAN
pm:uninstall <perl_nmodule>  # remove mod
pm:update <perl_module>      # update specified module

apiサーバが複数あるからsshして作業するわけにはいかないだろうから,capistranoも使えるようにする必要があるかもしれないなぁ.とまぁ,こんな記事を書いておいたら誰か作ってくれないかな,と淡い期待.それともこんな仕組みには誰も興味ないでしょうか.

micro typeでの準備

perl5 on AMI Linux t1.micro

awsを使ってapi.hakoniwa.com用の環境を作ってみようと思いました.試しに作ってみたときのメモです.用意したインスタンスは,

AMI: Amazon Linux AMI ID ami-2272864b (x86_64)
Name: Basic 64-bit Amazon Linux AMI 1.0

です.まずはインスタンスsshして

$ ssh -i .ssh/awskey -o ServerAliveInterval=5 ec2-user@ec2- ... amazonaws.com

後は普段通りにperl5をインストールします.

$ sudo yum update
$ sum yum groupinstall 'Development Tools'
$ sudo yum install git
$ curl -LO http://xrl.us/perlbrew
$ chmod +x perlbrew
$ ./perlbrew install
$ /home/ec2-user/perl5/perlbrew/bin/perlbrew init

ここまでできたら,~/.bashrcに以下の行を追加します.

source /home/ec2-user/perl5/perlbrew/etc/bashrc

さらに作業を続けます.

$ . .bashrc
$ perlbrew install perl-5.12.2
$ perlbrew switch perl-5.12.2
$ curl -L http://cpanmin.us/ | perl - App::cpanminus
$ cpanm  App::cpanoutdated
$ cpanm  App::pmuninstall
$ cpan-outdated | cpanm

これでperl5がインストールできました.

perl6 on AMI Linux t1.micro

使うつっもりはまだないですが,JPerl Advent Calendar 2010 Perl6 Tracを読んでperl6にも興味を持ちました.perl5をインストールしたついでにperl6のインストール方法もまとめてみました.AMIは上と同じようにt1.microを使っています.

$ sudo yum update
$ sudo yum groupinstall 'Development Tools'
$ sudo yum install git
$ sudo yum install libicu-devel

m1.typeの環境ではperl6作成の時にメモリがたりず,oom killerが発動します.そのためswap領域を追加しておきます.

$ sudo dd if=/dev/zero of=/myswap0 bs=1M count=2048
$ sudo mkswap /myswap0
$ sudo swapon /myswap0

これで,/proc/swapsを見ると以下のようになっているはずです.

Filename                    Type          Size     Used     Priority
/myswap0                                file          2097148     0     -1

次にbootした時にもswapを使えるようにするためには/etc/fstabに以下の行を追加しておきます.

/myswap0     swap     swap     defaults     0     0

これで準備ができました.AMI Linuxにはperl5が入っているので,そのまま使えば…と思ったのですが,perl6を作る際にエラーが出たので上と同じようにインストールしました.

$ curl -LO http://xrl.us/perlbrew
$ chmod +x perlbrew
$ ./perlbrew install
$ /home/ec2-user/perl5/perlbrew/bin/perlbrew init
## Add next line to the ~/.bashrc
source /home/ec2-user/perl5/perlbrew/etc/bashrc
$ . .bashrc
$ perlbrew install perl-5.12.2
$ perlbrew switch perl-5.12.2

さぁこれでperl6を作る準備ができました.

$ git clone git://github.com/rakudo/rakudo.git
$ cd rakudo
$ perl Configure.pl--gen-parrot
$ make
$ make install

Configure.plには15分ぐらい,makeには7時間ぐらいかかります.これでできるperl6はrakudo環境のみでrakudo starディストリビューションに入っているモジュールが入っていません(ファイルははいってるんだけど,ライブラリパスとは違う場所に入っていたり,そもそもなかったり).そのため公開されているモジュールを使ってみよう - JPerl Advent Calendar 2010 Perl6 Trackで紹介されているneutroコマンドを使って必要なモジュールをインストールする必要があります.
といっても,モジュールがないことに気づいてrakudo starに切り替えてそのあとにneutroのことを知ったので,実際に試していません.もし違っていたら,ここの文章を書き直すようにします.

chroot環境を作る

perlも作れたし,前回のメモでforkを禁止するモジュールも作ったし,後やらないといけないのは/chroot環境を作ればllevalを移植できるはず.ということでchroot環境を作ってperlが動くところまで試してみました.

$ sudo mkdir /chroot
$ sudo mkdir /chroot/bin
$ sudo mkdir /chroot/lib64
$ sudo mkdir /chroot/usr
$ sudo mkdir /chroot/tmp
$ sudo mkdir /chroot/dev
$ sudo mkdir /chroot/etc
$ sudo mkdir /chroot/home
$ sudo mknod -m 666 /chroot/dev/null c 1 3
$ sudo mknod -m 666 /chroot/dev/zero c 1 5
$ sudo mknod -m 666 /chroot/dev/random c 1 8

と,これで必要最低限必要なディレクトリとファイルを作りました.まずはbashとlsが動くようにライブラリをコピーしましょう.ライブラリはlddで探すことができます.

$ ldd /bin/ls:
     linux-vdso.so.1 =>  (0x00007fff1698b000)
     libselinux.so.1 => /lib64/libselinux.so.1 (0x0000003fc4a00000)
     librt.so.1 => /lib64/librt.so.1 (0x0000003fc7200000)
     libcap.so.2 => /lib64/libcap.so.2 (0x0000003fca200000)
     libacl.so.1 => /lib64/libacl.so.1 (0x0000003fc7600000)
     libc.so.6 => /lib64/libc.so.6 (0x0000003fc4200000)
     libdl.so.2 => /lib64/libdl.so.2 (0x0000003fc4600000)
     /lib64/ld-linux-x86-64.so.2 (0x0000003fc3e00000)
     libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003fc4e00000)
     libattr.so.1 => /lib64/libattr.so.1 (0x0000003fc6e00000)
$ ldd /bin/bash
     linux-vdso.so.1 =>  (0x00007fffeecaf000)
     libtinfo.so.5 => /lib64/libtinfo.so.5 (0x0000003fc6600000)
     libdl.so.2 => /lib64/libdl.so.2 (0x0000003fc4600000)
     libc.so.6 => /lib64/libc.so.6 (0x0000003fc4200000)
     /lib64/ld-linux-x86-64.so.2 (0x0000003fc3e00000)

ここに出て来た/lib64配下のライブラリを/chroot/lib64にコピーします.んで,

sudo chroot --userspec=ec2-user /chroot

とすることでchroot環境に移ります.

$ sudo chroot --userspec=ec2-user /chroot
bash-4.1$ ls
bin  dev  etc  home  lib  lib64  tmp  usr
bash-4.1$ cd 
bash-4.1$ pwd
/home/ec2-user
bash-4.1$ exit
exit

ちなみに,色々入れた後に確認した結果を書いてるので,手順通りに作った場合とは表示が違うかもしれません.次にbashやlsと同じようにperlでもライブラリを調べて

$ ldd `which perl`
     linux-vdso.so.1 =>  (0x00007fff9b5ff000)
     libnsl.so.1 => /lib64/libnsl.so.1 (0x0000003fc8e00000)
     libdl.so.2 => /lib64/libdl.so.2 (0x0000003fc4600000)
     libm.so.6 => /lib64/libm.so.6 (0x0000003fc5600000)
     libcrypt.so.1 => /lib64/libcrypt.so.1 (0x0000003fc5a00000)
     libutil.so.1 => /lib64/libutil.so.1 (0x0000003fc9200000)
     libc.so.6 => /lib64/libc.so.6 (0x0000003fc4200000)
     /lib64/ld-linux-x86-64.so.2 (0x0000003fc3e00000)
     libfreebl3.so => /lib64/libfreebl3.so (0x0000003fc5e00000)

コマンドをいくつか実行してみて動かなかったらstraceを使って足りないライブラリを見つけては足して行くという作業を続けました.そして今の環境はこんな感じ.

$ tree --filelimit 18 /chroot
/chroot
├── bin
│     ├── bash
│     ├── curl
│     ├── ls
│     └── ping
├── dev
│     ├── null
│     ├── random
│     └── zero
├── etc
│     ├── group
│     ├── host.conf
│     ├── hosts
│     ├── localtime
│     ├── nsswitch.conf
│     ├── passwd
│     └── resolv.conf
├── home
│     └── ec2-user
│             └── perl5
...
├── lib64
│     ├── ld-linux-x86-64.so.2
│     ├── libacl.so.1
│     ├── libattr.so.1
│     ├── libcap.so.2
│     ├── libcrypt.so.1
│     ├── libc.so.6
│     ├── libdl.so.2
│     ├── libfreebl3.so
│     ├── libm.so.6
│     ├── libnsl.so.1
│     ├── libnss_dns.so.2
│     ├── libnss_files.so.2
│     ├── libpthread.so.0
│     ├── libresolv.so.2
│     ├── librt.so.1
│     ├── libselinux.so.1
│     ├── libtinfo.so.5
│     └── libutil.so.1
├── tmp
└── usr
    ├── bin
    │      ├── id
    │      └── strace
    ├── lib
    │      └── locale
    │               └── locale-archive
    └── lib64
        ├── libcurl.so.4
        └── libidn.so.11

29 directories, 44 files

この状況でchrootしてperlを動かすと,

$ sudo chroot --userspec=ec2-user /chroot
bash-4.1$ export PATH=/home/ec2-user/perl5/perlbrew/perls/perl-5.12.2/bin/:$PATH
bash-4.1$ perl -v

This is perl 5, version 12, subversion 2 (v5.12.2) built for x86_64-linux

Copyright 1987-2010, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

ってことで,llevalでの特徴となっているLWP::Simpleが動くかどうかを試してみると,

bash-4.1$ perl -MLWP::Simple -le 'getprint "http://www.perl.com"'
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="generator" content="Movable Type Pro 5.02" />
<link rel="stylesheet" href="http://www.perl.com/pub/styles.css" type="text/css" />
<link rel="start" href="http://www.perl.com/pub/" title="Home" />
...

となるので,成功です.このとき,/etc/passwdは

ec2-user:x:222:500:EC2 Default User:/home/ec2-user:/bin/bash

同様に/etc/groupは

ec2-user:x:500:

としています.
ちなみに,今気になっているのはidコマンドの結果です.普通は

$ id
uid=222(ec2-user) gid=500(ec2-user) groups=500(ec2-user),10(wheel)

となるはずですが,chroot環境では

bash-4.1$ id
uid=222(ec2-user) gid=0 groups=500(ec2-user),0,1,2,3,4,6,10

となっています.なぜに0とかがでてくるのかよくわかっていません.大丈夫なのかな?