Route 53でcli53を使ったDynamic DNS
Route 53でDDNSというとroute53DynDNS.bashを使った例を多く見かけるのだが、自分の手元では正常動作しないし読めた物ではない感じの作りなので、cli53を使って極めて簡単に書き直した。
AWSインスタンスでの例はこちらにあるのだが、ここではAWSではない自宅鯖のDDNSに用いるのでEC2関係の所は参考にしない。
cli53をインストール
sudo pip install cli53
.botoにIDとKeyを設定
こんな感じの奴を作る
[Credentials] aws_access_key_id = <your_access_key_here> aws_secret_access_key = <your_secret_key_here>
update53.shを作成
#!/bin/sh ZONE=example.com RECORD=www DAT=/tmp/update53.dat if [ -f $DAT ]; then PREV=`cat $DAT` else PREV=`cli53 rrlist $ZONE|grep ^$RECORD|awk '{print $5}'` fi CUR=`wget -q -O - checkip.dyndns.org|sed -e 's/.*Current IP Address: //' -e 's/<.*$//'` echo $CUR > $DAT if [ "$PREV" == "$CUR" ]; then exit 0 fi cli53 rrcreate $ZONE $RECORD A $CUR --ttl 3600 --replace
crontabに適当にインストール
*/5 * * * * /home/syuu/update53.sh
mrubyでUEFIブートローダなやつのバイナリ配布
多分みなさんEDK2のビルドが面倒い最高で試すの嫌だと思うので、バイナリ配布しておきますね。
こちらです:
https://s3.amazonaws.com/syuu1/mruby.efi
ブートローダのスクリプトはこちら:
https://raw.githubusercontent.com/syuu1228/mruby_on_efi_shell/devel/example/bootloader.rb
いきなりefibootmgrなどを使ってブートメニューに組み込もうとせずに、EFI Shellから起動することを強くおすすめします(しくってブート出来なくなったりすると困ると思うので)。
GCEでOSvを動かそう
OSvは、ハイパーバイザやIaaSプラットフォームへアプリケーションをデプロイすることに特化した軽量OSです(※詳しくはこちら)。
今回はRuby on Railsで書かれたブログエンジン「Publify」をインストールしたOSvのイメージをGoogle Compute Engine(GCE)へデプロイしてみます。
プロジェクトIDの設定
先ほど作成したプロジェクトへログインしてIDを設定します。
gcloud auth login gcloud config set project fresh-mason-798
バケットの作成
OSvのディスクイメージをアップロードするためにCloud Storageへバケットを作成します。
gsutil mb gs://osv_test
Rubyのインストール
Publify入りのディスクイメージをビルドするためにrvmでRubyをインストールします。
gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3 \curl -sSL https://get.rvm.io | bash -s stable source ~/.profile rvm install 2.1.4 rvm use 2.1.4 sudo yum install sqlite-devel
OSvイメージのビルド
OSvのソースコードをcloneしてPublifyのイメージをビルドします。
ビルドが終わったらGCE用のtar.gzファイルを生成します。
git clone https://github.com/cloudius-systems/osv.git git submodule update --init --recursive make image=ruby-publify ./scripts/gen-gce-tarball.sh
※今回は、make image=ruby-publify,httpserverとしたところエラーが出て起動しなかった(Rubyとの組み合わせで発生するバグ)のでhttpserverを外しましたが、これがないと8000番ポートにRESTサーバが起動しないのでOSvの管理が著しく不便になります。Ruby以外のアプリを使用する時は必ず付けることをオススメします。
OSvイメージのデプロイ
gsutil・gcutilコマンドを使ってgen-gce-tarball.shコマンドで生成したosv.tar.gzをアップロード、デプロイします。
今回はus-central1-aのf1-microへosvという名前で実行することにしました。
gsutil cp build/release/osv.tar.gz gs://osv_test/osv.tar.gz gcutil addimage osv gs://osv_test/osv.tar.gz gcutil addinstance --image=osv --machine_type=f1-micro --zone=us-central1-a osv
動作状況の確認
getserialportoutputでOSvインスタンスの動作状況を確認します。
gcutil getserialportoutput osv
以下のような出力が表示されます。
(略) OSv v0.16-22-ga6c87f0 eth0: 10.240.159.233 sigaltstack() stubbed => Booting Thin => Rails 3.2.21 application starting in production on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server You did not specify how you would like Rails to report deprecation notices for your production environment, please set config.active_support.deprecation to :notify at config/environments/production.rb WARNING: fcntl(F_SETLK) stubbed Thin web server (v1.6.3 codename Protein Powder) Maximum connections set to 1024 Listening on 0.0.0.0:3000, CTRL+C to stop
ファイヤウォールの設定
グローバルIPからポート3000へ接続出来るように設定を変更します。
gcutil addfirewall publify --description="publify http" --allowed="tcp:3000"
覗いてみる
http://146.148.55.173:3000/ で起動中です(そのうち落とします)。
まとめ
OSvのイメージをGCEへデプロイする方法を解説しました。
今回はPublifyの例を挙げましたが、CassandraのようなJavaアプリやRedisのようなネイティブアプリも同様の手順でデプロイすることが可能です。
UEFI Native API on mruby
mrubyでブートローダを書けるようにしました - かーねる・う゛いえむにっきの記事はlibc API経由でmrubyからUEFIの機能を使う話でした。
これとは別に、mruby on efi shellにはUEFIのネイティブAPIへアクセスするためのクラスが用意されているという話をします。
mruby on efi shellのソースコードを眺めていると、二つのスクリプトが見つかります(こちら)。
一つはdump_cmos.rbで、UEFI::LowLevel.io_read8/write8を呼び出してIOポートアクセスを行っている事が分かります。
################################################3 # Example # CMOS dump. class String def rjust(width, padding=' ') str = "" if (self.length < width) str = padding * (width - self.length) end return str + self end end CMOS_INDEX = 0x70 CMOS_DATA = 0x71 orig_index = UEFI::LowLevel.io_read8(CMOS_INDEX) (0..0x7F).to_a.each_with_index do |i, index| UEFI::LowLevel.io_write8(CMOS_INDEX, i) v = UEFI::LowLevel.io_read8(CMOS_DATA) print "#{v.to_s(16).rjust(2, '0')} " print "\n" if (index % 16 == 15) end UEFI::LowLevel.io_write8(CMOS_INDEX, orig_index)
もう一つがread_disk.rbで、こちらはUEFI::ProtocolやUEFI::BootService、UEFI::Guidなどのクラスを用いてEFI_DISK_IO_PROTOCOLへアクセスし、メディア情報を取得しています。
# Sample of BLOCK_IO_PROTOCOL def print_binary_data(data) data.bytes.each_slice(16) do |data| # print hex. data.each do |i| print i.to_s(16).rjust(2, "0").upcase print " " end print " " # print ascii. data.each do |i| print (0x20 <= i && i <= 0x7F) ? i.chr : "." end puts "" end puts "" end class BlockIoProtocol < UEFI::Protocol GUID = UEFI::Guid.new("964e5b21-6459-11d2-8e39-00a0c969723b") define_variable(:revision, :u64) define_variable(:media, :p) define_function(:reset, :e, [:p, :b]) define_function(:read_blocks, :e, [:p, :u32, :u64, :u64, :p]) #... end # I will rename UEFI::Protocol to UEFI::Structure or any other name... class Media < UEFI::Protocol define_variable(:media_id, :uint32) #... end # See http://wiki.phoenix.com/wiki/index.php/EFI_DISK_IO_PROTOCOL class DiskIoProtocol < UEFI::Protocol GUID = UEFI::Guid.new("CE345171-BA0B-11d2-8e4F-00a0c969723b") define_variable(:revision, :u64) define_function(:read_disk, :efi_status, [:p, :u32, :u64, :u64, :p]) #... end # Find first disk. handles = UEFI::BootService.locate_handle_buffer(BlockIoProtocol::GUID) handle = handles.first puts "handle: #{handle}" # Locate BlockIoProtocol to get the pointer of media. ptr = UEFI::BootService.handle_protocol(handle, BlockIoProtocol::GUID) bp = BlockIoProtocol.new(ptr) # Get media. media = Media.new(bp.media) puts "media_id: #{media.media_id}" # Find DiskIoProtocol related to bp. ptr = UEFI::BootService.handle_protocol(handle, DiskIoProtocol::GUID) dp = DiskIoProtocol.new(ptr) buf = " " * 512 # Buffer to be filled with returned data. st = dp.read_disk(ptr, media.media_id, 0, buf.size, buf) if (st.success?) print_binary_data(buf) else puts "ERROR: #{st}" end
プロトコルのハンドルを作るところの操作でクラスを継承してインタフェースを動的に定義するなどの違いはありますが、概ねEDK2に含まれるCで書かれたUEFIのサンプルアプリと同様のインタフェースを持っている事が分かります。
C拡張のコード(mruby_on_efi_shell/src at master · masamitsu-murase/mruby_on_efi_shell · GitHub)を眺めてみると、UEFI::Handle, UEFI::Pointer, UEFI::Protocol, UEFI::Status, UEFI::BootService, UEFI::LowLevel, UEFI::RuntimeServiceのようなクラスが定義されています。
このインタフェースもEDK2で使われているものと大体一致しているので、完全ではないかもしれないがCのコードを翻訳していけばだいたいmrubyで記述出来そうだと予想されます。
今回はここまでで、次回何らかのサンプルコードを書いてみることにします。
mrubyでブートローダを書けるようにしました
カーネル/VM@関西7回目でお話したネタです。スライドはこちら:
既にmrubyはUEFIへ移植されています(覚え書き: mruby on EFI Shell)。
また、EFI StubカーネルをUEFIアプリから実行するのは非常に簡単であることが分かっています(実際にソースコードを書いて実験しました)。
以上のことから、「mrubyにEFI Stubカーネルのファイル名を指定したら実行するAPIを足せば、mrubyでブートローダが実装可能になるに違いない」と考えました。
ただ面白いというだけでなく、mruby-socket・mruby-simplehttp-socketなどのmrbgemsを移植していけばブート処理を記述したmrubyスクリプトやカーネルそのものをHTTPから取得することが出来るようになり、簡単なスクリプトで高度にカスタマイズされたブート処理をOSに依存せず行う事が出来るようになると思い、まずはたたき台として単純なブートメニューを実現するものを実装してみました。
以下に今回実装したブートローダのmrubyスクリプトを貼ります(Dirクラスのコードは一部省略しました。フルサイズのコードはこちら)。
base_dir = "kernels" kernel_options = "root=/dev/sda3" kernels = [] Dir.foreach(base_dir) do |ent| next if ent == "." || ent == ".." kernels << ent end puts "** Boot Menu **" (0...kernels.length).each do |i| puts "[#{i}] #{kernels[i]}" end loop do puts "Input number:" key = Shell.gets.chomp idx = key.to_i if kernels.length <= idx next end puts "#{base_dir}\\#{kernels[idx]} #{kernel_options}" Shell.exec "#{base_dir}\\#{kernels[idx]} #{kernel_options}" break end
ここでは、ブートローダを実現するために「Dir」と「Shell」クラスを実装しています。
(mruby_on_efi_shellでは殆どのモジュールが無効化されている上にmrbgemsのビルドが封じられており、更にLinuxと完全に同等のAPIを提供してはいないので、ビルドスクリプトを編集したりAPIを叩いて1クラスごとに定義していく必要がありました。)
Dirクラスはmruby-dirから最低限必要なコードを抜き出してきたものです。
mruby_on_efi_shell/dir.c at devel · syuu1228/mruby_on_efi_shell · GitHub
普通にreaddir()などが使えているのはEDK2のlibcを使用しているからです。今回の作業では基本的にlibcのAPIを使用しています。
(このlibcは実験版扱いで、UEFIアプリケーションの正式なAPIではなく実験的な実装です。アプリをUEFIへポーティングする際には便利で、mruby_on_efi_shellもこれを利用して移植されています。よって、今回はこれに頼ることにしました。)
Shellクラスは今回独自に実装したもので、libc関数であるsystem(3)・fgets(3)の単純なラッパ関数(それぞれShell.exec, Shell.gets)を提供します。
以下にコードを示します。
#include "mruby.h" #include "mruby/class.h" #include "mruby/data.h" #include "mruby/string.h" #include "error.h" #include <stdlib.h> #include <stdio.h> #include "uefi.h" mrb_value mrb_shell_exec(mrb_state *mrb, mrb_value klass) { mrb_value command; char *ccommand; int ret; mrb_get_args(mrb, "S", &command); ccommand = mrb_string_value_cstr(mrb, &command); ret = system(ccommand); return mrb_fixnum_value(ret); } mrb_value mrb_shell_gets(mrb_state *mrb, mrb_value klass) { char buf[65535]; if (fgets(buf, 65535, stdin) == NULL) return mrb_nil_value(); return mrb_str_new_cstr(mrb, buf); } void mrb_init_shell(mrb_state *mrb) { struct RClass *shell; shell = mrb_define_class(mrb, "Shell", mrb->object_class); MRB_SET_INSTANCE_TT(shell, MRB_TT_DATA); mrb_define_class_method(mrb, shell, "exec", mrb_shell_exec, ARGS_REQ(1)); mrb_define_class_method(mrb, shell, "gets", mrb_shell_gets, ARGS_NONE()); }
本当はUEFIネイティブなAPIを使うともう少し複雑なAPIコールが必要になるのですが、ここではlibc関数でその部分をラップすることで関数コール1つだけの単純なコードになっています。
上述のmrubyスクリプトの通り、fgets(stdin)でキーボード入力を取って起動するカーネルのファイル名を決め、system()にカーネルファイル名と引数を渡して実行することでカーネルが起動しています。
というわけで、実に簡単なハックでブートローダがmrubyで書けるようになりました。
これだけではAPIが足りませんし、コードが極めてとっちらかっているので、もう少し整理していきたいと考えています。
現状のコードはこちらで公開しています:
GitHub - syuu1228/mruby_on_efi_shell at devel
※このコードではEFI Stubカーネル(Linux)や各種UEFIブートローダ(拡張子.EFIのPEバイナリ)のみがブート対象です。それ以外のものは例えUEFI対応カーネルでもブートできません。 多くのディストリビューションがEFI Stubカーネルを提供しているのでこれで必要充分だろうと判断しました。
OSvのビルド環境の構築
OSvはMacやWindows上のVirtualBox・VMware、VMware ESXiなど様々な環境で動作するのですが、ビルド環境はFedoraの最新版という極めて限られた環境でのみテストされています(理屈の上では他のディストリビューションでもビルド可能ですが、細かなパッケージの差異などのためにエラーを起こすことがあります)。
より多くの環境でビルド出来るようにビルドスクリプトを手直しするのが本筋なのですが、ここでは取り敢えずFedora環境を用意して開発を始めることを目的とします。
VMware上での開発
Windows・Macではこの方法を選択する必要があります。
Linux環境でもこの方法が使用出来ますが、後述のchroot環境を使った方が高いパフォーマンスが得られます。
VMwareをインストールしてFedoraのディスクイメージを起動します。
(ネステッドVMを使用するため、VMwareでなくてはなりません。VirtualBoxなどでは動作しません。)
VMイメージはここで配布しています。
ユーザ名とパスワードはroot:osvdev, osvdev:osvdevです。
osvdevユーザでログインを行いTerminalを立ち上げ、以下のコマンドを実行することにより開発作業を始められます。
cd osv git pull make -j4 ./scripts/run.py -n
chroot環境での開発
Fedora以外のLinuxでの開発にはこの方法がおすすめです。
chrootディレクトリにFedoraをインストールします。
GitHub - syuu1228/fedora-chroot-image: Fedora chroot image distribution and build scriptにてchrootイメージを配布しているのでこちらを使用します。
wget -O fedora-chroot.tar.xz "https://github.com/syuu1228/fedora-chroot-image/blob/master/fedora-chroot.tar.xz?raw=true" sudo tar -xpf fedora-chroot.tar.xz sudo ./run-fedora.sh
chrootディレクトリ内でrootユーザのシェルが実行されます。
cd ~ ./bootstrap.sh
最小限必要なパッケージをインストールします。
yum install git git clone https://github.com/cloudius-systems/osv.git cd osv ./scripts/setup.py git submodule update --recursive --init make -j4 ./scripts/run.py -n
OSvのソースコードをダウンロードしてビルドを行います。
1コマンドでJavaアプリをOSvへデプロイ
OSvは、ハイパーバイザやIaaSプラットフォームへアプリケーションをデプロイすることに特化した軽量OSです(※詳しくはこちら)。
また、CapstanはGo言語で実装されたOSvのデプロイツールです(※詳しくはこちら)。
OSvはJVM上で動くアプリケーションをメインターゲットとして開発が進められており、様々なJavaアプリケーションが動作します。
この記事ではCapstanの開発版を使って1コマンドで任意のJavaアプリからOSvのディスクイメージを生成&実行してみます。
Capstan開発版をインストール
go get github.com/cloudius-systems/capstan go install github.com/cloudius-systems/capstan
これで~/go/bin/にCapstanがインストールされました。
PATH指定を省略してCapstanを呼びたい場合は~/go/binにPATHを通して下さい。
その際、既にバイナリ配布版のCapstanをインストールしている場合はPATHが異なることに注意して下さい(バイナリ配布版は~/bin)。
Rhinoを試す
まずは単一のjarで配布されているRhino Javascriptインタプリタを試します。
wget ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_7R2.zip unzip -x rhino1_7R2.zip ~/go/bin/capstan run rhino1_7R2/js.jar
capstan run
実行が開始されると、OSvの起動メッセージの後にRhinoが起動してくるのでJavaScriptで遊んでみます。
Downloading cloudius/osv-openjdk/index.yaml... 160 B / 160 B [=====================================================] 100.00 % 0 Downloading cloudius/osv-openjdk/osv-openjdk.qemu.gz... 74.34 MB / 74.34 MB [===========================================] 100.00 % 3m16s Building js... Uploading files... 1 / 1 [=============================================================] 100.00 % 0Created instance: js OSv v0.16 eth0: 192.168.122.15 Rhino 1.7 release 2 2009 03 22 js> print("Hello World"); Hello World js> 1 + 2; 3 js> quit();
複数のjarファイルからなるプログラムを実行してみる
One-JARを使うと複数のjarファイルを一つにまとめることが出来るようなので、これを使ってApache FtpServerを一つのjarにまとめてOSvへデプロイしてみます。
まずFtpServerとOne-JARをダウンロードしてきて解凍します。
wget http://ftp.riken.jp/net/apache/mina/ftpserver/1.0.6/dist/ftpserver-1.0.6.zip unzip -x ftpserver-1.0.6.zip cd apache-ftpserver-1.0.6 wget http://jaist.dl.sourceforge.net/project/one-jar/one-jar/one-jar-0.97/one-jar-boot-0.97.jar
まとめたjarファイルを作るための一時ディレクトリを./rootに作成し、ここのjarファイルをコピーします。
mkdir -p root/main mkdir -p root/lib cp common/lib/*.jar root/lib mv root/lib/ftpserver-core-1.0.6.jar root/main/ cd root jar xvf ../one-jar-boot-0.97.jar
boot-manifest.mf
boot-manifest.mfにOne-Jar-Main-Classの行を足します。
Manifest-Version: 1.0 Main-Class: com.simontuffs.onejar.Boot One-Jar-Main-Class: org.apache.ftpserver.main.CommandLine
capstan runを呼んでディスクイメージを生成・実行します。
FTPサーバに接続可能にするため、ネットワークをブリッジ接続にします。
jar -cvfm ../ftpserver.jar boot-manifest.mf . cd .. ~/go/bin/capstan run -n bridge ftpserver.jar
capstan runが実行されると以下のような感じでFtpServerが起動します。
Building ftpserver... Uploading files... 1 / 1 [=============================================================] 100.00 % 0Created instance: ftpserver OSv v0.16 eth0: 192.168.122.77 Using default configuration log4j:WARN No appenders could be found for logger (org.apache.ftpserver.impl.DefaultFtpServerContext). log4j:WARN Please initialize the log4j system properly. FtpServer started
ftpクライアントで繋いでみます。
$ ftp 192.168.122.77 Connected to 192.168.122.77 (192.168.122.77). 220 Service ready for new user. Name (192.168.122.77:syuu): anonymous 331 Guest login okay, send your complete e-mail address as password. Password: 530 Authentication failed. Login failed. ftp>
…デフォルト設定の場合に有効なユーザ名・パスワードが分からなかったのでログイン失敗しましたが、FTPサーバが上がっているようです。